TLDR:
使用通用属性属性构建类的最佳方法是什么?可以在构建这些属性时指定getter和setter?
背景:
我正在尝试用Python创建一个SCPI命令结构的面向对象表示。解析后,命令层是(子)对象,设置/获取最低命令对象实际上将与设备通信以设置/获取其参数。
我遇到的问题是将通用getter / setter函数赋值给command-object,就像属性一样。我在__setattr__
/ __getattr__
度过了一段艰难的时光,因为我总是遇到递归问题。
如果在类构建期间及其实例化/运行时版本中设置/获取属性存在区别,那将会很酷。甚至更好:是否可以将属性设为属性“对象”?类似的东西:
foo = property()
foo.__getter__ = lambda paramName : devcontroller.request(paramName)
foo.__setter__ = lambda paramName, paramValue : devcontroller.demand(paramName, paramValue)
setattr(self, paramName, foo)
但显然这是不可能的(只读)。
现在最好的解决方案是下面的解决方案,但感觉非常笨重。例如,不能用Tab键完成命令对象。
class SCPITree(object):
def __init__(self, cmd, branch):
self.__dict__['building'] = True
self.__dict__['params'] = {}
# parse tree
for k, v in branch.items():
catCmd = '{}:{}'.format(cmd, k[1])
param = k[0]
if isinstance(v, dict):
# still a dictionary, so unpack
#setattr(self, param, SCPITree(catCmd, v))
self.__dict__[param] = SCPITree(catCmd, v)
else:
# have the value type, so this is a leaf
print('{} of {}'.format(catCmd, v))
self.__dict__['params'][param] = {'cmd': catCmd, 'type': v}
#setattr(self, param, v())
del self.__dict__['building']
def __getattr__(self, name):
'''Requests the device for parameter'''
param = self.__dict__['params'].get(name)
if param:
request = param['cmd']+'?'
print('Requesting device with: {}'.format(request))
# do actual device communication here
return 'Response from device, converted to {}'.format(param['type'])
else:
if 'building' not in self.__dict__:
raise KeyError('Device has no SCPI command for {}'.format(name))
def __setattr__(self, name, value):
'''Sets a device parameter'''
param = self.__dict__['params'].get(name)
if param:
demand = param['cmd']+' {}'.format(value)
print('Demanding device with: {}'.format(demand))
# do actual device communication here
else:
if 'building' not in self.__dict__:
raise KeyError('Device has no SCPI command for {}'.format(name))
if __name__ == '__main__':
# test SCPI tree parsing
trunk = {
('input', 'INP'):{
('agc', 'AGC'):{
('mode', 'MOD'):str,
('refLevelDB', 'REF'):float},
('delayMS', 'DEL'):float,
('freqMHz', 'FREQ'):float}}
scpi = SCPITree('DCS', trunk)
在运行代码之后,交互是这样的:
DCS:INP:AGC:MOD of <type 'str'>
DCS:INP:AGC:REF of <type 'float'>
DCS:INP:DEL of <type 'float'>
DCS:INP:FREQ of <type 'float'>
In [1]: scpi.input.delayMS
Requesting device with: DCS:INP:DEL?
Out[1]: "Response from device, converted to <type 'float'>"
In [2]: scpi.input.delayMS = 3
Demanding device with: DCS:INP:DEL 3
In [3]: scpi.input.params
Out[3]:
{'delayMS': {'cmd': 'DCS:INP:DEL', 'type': float},
'freqMHz': {'cmd': 'DCS:INP:FREQ', 'type': float}}