55
66import logging
77import os
8- from functools import wraps
98from types import MethodType
10- from typing import TYPE_CHECKING , Any , Callable , Literal
9+ from typing import TYPE_CHECKING , Any , Callable , Literal , cast
10+
11+ from qcodes .utils import is_function
1112
1213from .command import Command
1314from .parameter_base import ParamDataType , ParameterBase , ParamRawDataType
2223log = logging .getLogger (__name__ )
2324
2425
26+ def _get_parameter_factory (
27+ function : Callable [[str ], ParamRawDataType ] | None ,
28+ cmd : str | Callable [[], ParamRawDataType ] | None ,
29+ parameter_name : str ,
30+ ) -> Callable [[Parameter ], ParamRawDataType ]:
31+ if cmd is None :
32+
33+ def get_manual_parameter (self : Parameter ) -> ParamRawDataType :
34+ if self .root_instrument is not None :
35+ mylogger : InstrumentLoggerAdapter | logging .Logger = (
36+ self .root_instrument .log
37+ )
38+ else :
39+ mylogger = log
40+ mylogger .debug (
41+ "Getting raw value of parameter: %s as %s" ,
42+ self .full_name ,
43+ self .cache .raw_value ,
44+ )
45+ return self .cache .raw_value
46+
47+ return get_manual_parameter
48+
49+ elif isinstance (cmd , str ) and is_function (function , 1 ):
50+ # cast is safe since we just checked this above using is_function
51+ function = cast (Callable [[str ], ParamRawDataType ], function )
52+
53+ def get_parameter_ask (self : Parameter ) -> ParamRawDataType :
54+ # for some reason mypy does not understand
55+ # that cmd is a str even if this is defined inside
56+ # an if isinstance block
57+ assert isinstance (cmd , str )
58+ return function (cmd )
59+
60+ return get_parameter_ask
61+
62+ elif is_function (cmd , 0 ):
63+ # cast is safe since we just checked this above using is_function
64+ cmd = cast (Callable [[], ParamRawDataType ], cmd )
65+
66+ def get_parameter_func (self : Parameter ) -> ParamRawDataType :
67+ return cmd ()
68+
69+ return get_parameter_func
70+
71+ elif isinstance (cmd , str ) and function is None :
72+ raise TypeError (
73+ f"Cannot use a str get_cmd without "
74+ f"binding to an instrument. "
75+ f"Got: get_cmd { cmd } for parameter { parameter_name } "
76+ )
77+
78+ else :
79+ raise TypeError (
80+ "Unexpected options for parameter get. "
81+ f"Got: get_cmd { cmd } for parameter { parameter_name } "
82+ )
83+
84+
2585class Parameter (ParameterBase ):
2686 """
2787 A parameter represents a single degree of freedom. Most often,
@@ -172,7 +232,7 @@ def __init__(
172232 instrument : InstrumentBase | None = None ,
173233 label : str | None = None ,
174234 unit : str | None = None ,
175- get_cmd : str | Callable [..., Any ] | Literal [False ] | None = None ,
235+ get_cmd : str | Callable [[], ParamRawDataType ] | Literal [False ] | None = None ,
176236 set_cmd : str | Callable [..., Any ] | Literal [False ] | None = False ,
177237 initial_value : float | str | None = None ,
178238 max_val_age : float | None = None ,
@@ -211,25 +271,10 @@ def _set_manual_parameter(
211271 self .cache ._set_from_raw_value (x )
212272 return x
213273
214- def _get_command_caller (parameter : Parameter , command : Command ) -> MethodType :
215- @wraps (Command .__call__ )
216- def call_command (self : Parameter ) -> Any :
217- return command ()
218-
219- return MethodType (call_command , parameter )
220-
221- def _set_command_caller (parameter : Parameter , command : Command ) -> MethodType :
222- @wraps (Command .__call__ )
223- def call_command (self : Parameter , val : ParamRawDataType ) -> Any :
224- return command (val )
225-
226- return MethodType (call_command , parameter )
227-
228274 if instrument is not None and bind_to_instrument :
229275 existing_parameter = instrument .parameters .get (name , None )
230276
231277 if existing_parameter :
232-
233278 # this check is redundant since its also in the baseclass
234279 # but if we do not put it here it would be an api break
235280 # as parameter duplication check won't be done first,
@@ -281,27 +326,15 @@ def call_command(self: Parameter, val: ParamRawDataType) -> Any:
281326 " get_raw is an error."
282327 )
283328 elif not self .gettable and get_cmd is not False :
284- if get_cmd is None :
285- # ignore typeerror since mypy does not allow setting a method dynamically
286- self .get_raw = MethodType (_get_manual_parameter , self ) # type: ignore[method-assign]
287- else :
288- if isinstance (get_cmd , str ) and instrument is None :
289- raise TypeError (
290- f"Cannot use a str get_cmd without "
291- f"binding to an instrument. "
292- f"Got: get_cmd { get_cmd } for parameter { name } "
293- )
329+ exec_str_ask : Callable [[str ], ParamRawDataType ] | None = (
330+ getattr (instrument , "ask" , None ) if instrument else None
331+ )
332+
333+ self .get_raw = MethodType ( # type: ignore[method-assign]
334+ _get_parameter_factory (exec_str_ask , cmd = get_cmd , parameter_name = name ),
335+ self ,
336+ )
294337
295- exec_str_ask = getattr (instrument , "ask" , None ) if instrument else None
296- # ignore typeerror since mypy does not allow setting a method dynamically
297- self .get_raw = _get_command_caller ( # type: ignore[method-assign]
298- self ,
299- Command (
300- arg_count = 0 ,
301- cmd = get_cmd ,
302- exec_str = exec_str_ask ,
303- ),
304- )
305338 self ._gettable = True
306339 self .get = self ._wrap_get (self .get_raw )
307340
@@ -326,10 +359,11 @@ def call_command(self: Parameter, val: ParamRawDataType) -> Any:
326359 exec_str_write = (
327360 getattr (instrument , "write" , None ) if instrument else None
328361 )
362+ # TODO get_raw should also be a method here. This should probably be done by wrapping
363+ # it with MethodType like above
329364 # ignore typeerror since mypy does not allow setting a method dynamically
330- self .set_raw = _set_command_caller ( # type: ignore[method-assign]
331- self ,
332- Command (arg_count = 1 , cmd = set_cmd , exec_str = exec_str_write ),
365+ self .set_raw = Command ( # type: ignore[assignment]
366+ arg_count = 1 , cmd = set_cmd , exec_str = exec_str_write
333367 )
334368 self ._settable = True
335369 self .set = self ._wrap_set (self .set_raw )
0 commit comments