在另一个类中使用实例方法作为装饰器

时间:2018-05-31 23:49:09

标签: python mocking instance patch python-decorators

我正在尝试创建一个实例化串行对象的类(MySerial),以便我可以写入/读取串行设备(UART)。有一个实例方法是一个装饰器,它包含一个属于完全不同的类(App)的函数。因此装饰器负责写入和读取串行缓冲区。

如果我在MySerial类中创建App的实例,我就无法使用从MySerial创建的装饰器实例方法。 我已经尝试过前面的实例方法并使用this second answer中解释的类方法,但我确实需要实例化MySerial,因此使用__init__创建一个实例。

如何实现这一目标?这不可能吗?

  • 创建一个作为实例方法的装饰器。
  • 在另一个类
  • 中使用此装饰器
class MySerial():
    def __init__(self):
        pass # I have to have an __init__
    def write(self):
        pass # write to buffer
    def read(self):
        pass # read to buffer
    def decorator(self, func):
        def func_wrap(*args, **kwargs):
            self.write(func(*args, **kwars))
            return self.read()
        return func_wrap

class App():
    def __init__(self):
        self.ser = MySerial()

    @self.ser.decorator  # <-- does not work here.
    def myfunc(self):
        # 'yummy_bytes' is written to the serial buffer via 
        # MySerial's decorator method
        return 'yummy_bytes'

if __name__ == '__main__':
    app = App()

3 个答案:

答案 0 :(得分:1)

您可以使用staticmethod打包decoratorfunc_wrap的内部decorator函数在其签名中包含一个附加参数:clscls可用于访问ser实例的App属性,然后可以从{{1}调用所需的方法writeread }}。另请注意,在您的声明中,cls.ser不接受参数,但会传递包装函数的结果。以下代码使用MySerial.write来阻止原本会被引发的*args

TypeError

答案 1 :(得分:1)

这不起作用的原因是因为你在类体中引用了self,它没有定义。这是两个解决方案。

将序列对象存储为类属性

如果您将MySerial实例存储为属性,则可以在类主体中访问它:

class App():
    ser = MySerial()

    @ser.decorator
    def myfunc(self):
        return 'yummy_bytes'

在每个实例化上装饰

或者,如果每个MySerial实例需要不同的App实例,则需要等待创建实例以定义实例属性{{ 1}}。这意味着函数在每个实例创建时动态修饰,在这种情况下,my_func装饰器语法必须由函数调用替换。

@

此解决方案概括为装饰多种方法或有条件地停用序列化,例如在测试环境中。

class App():
    def __init__(self):
        self.ser = MySerial()
        self.my_func = self.ser.decorator(self.myfunc)

    def myfunc(self):
        return 'yummy_bytes'

答案 2 :(得分:0)

有很多隐藏的陷阱使这个设计变得有风险,但这是一个很好的学习范例。

首先,装饰时对'self'的调用失败,因为在该范围内没有自我。它只存在于方法中。现在,简单的一个就不在了......

myfunc是App类的一个属性。当您创建App的实例时,始终会调用一个函数。即使它变得有条理,也只会发生一次。

a1 = App()
a2 = App()
assert a1.myfunc.__func__ is a2.myfunc.__func__
assert id(a1.myfunc) is id(a2.myfunc)  # Methods have some weirdness that means that won't equate but id's show they are the same 

这就是为什么需要self来为实例获取唯一的命名空间。这也是为什么你不能以这种方式获得实例独有的装饰器的原因。 考虑它的另一种方法是必须在生成实例之前定义Class。因此,您无法在类的定义中使用实例。

解决方案

装饰器需要以不存储任何实例属性的方式编写。它将改为访问App实例属性。

class MySerial():
    def __init__(self):
        pass # Possibly don't need to have an __init__
    def write(self, serial_config):
        pass # write to buffer
    def read(self, serial_config):
        pass # read to buffer
    def decorator(self, func):
        def func_wrap(self_app: App, *args, **kwargs):
            self.write(func(self_app, *args, **kwars), self_app.serial_config)
            return self.read(self_app.serial_config)
        return func_wrap

ser = MySerial()

class App():
    def __init__(self, serial_config):
        self.serial_config = serial_config  # This is the instance data for     MySerial

    @ser.decorator
    def myfunc(self):
        # 'yummy_bytes' is written to the serial buffer via 
        # MySerial's decorator method
        return 'yummy_bytes'

if __name__ == '__main__':
    app = App()

现在我假设MySerial将有一个唯一的文件,或端口或每个App实例的东西。这是将在serial_config中记录的内容。如果流正在打开结束时,这可能不是很优雅,但您应该能够针对您的确切应用进行改进。