生成属性的方法不那么笨重?

时间:2018-03-02 13:25:23

标签: python generics properties getter-setter

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}}

0 个答案:

没有答案