简体   繁体   English

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

[英]Use an instance method as a decorator within another class

I am trying to create a class ( MySerial ) that instantiates a serial object so that I can write/read to a serial device (UART). 我正在尝试创建一个实例化串行对象的类( MySerial ),以便我可以写入/读取串行设备(UART)。 There is an instance method that is a decorator which wraps around a function that belongs to a completely different class ( App ). 有一个实例方法是一个装饰器,它包含一个属于完全不同的类( App )的函数。 So decorator is responsible for writing and reading to the serial buffer. 因此装饰器负责写入和读取串行缓冲区。

If I create an instance of MySerial inside the App class, I can't use the decorator instance method that is created from MySerial . 如果我在App类中创建一个MySerial实例,我就不能使用从MySerial创建的装饰器实例方法。 I have tried foregoing instance methods and using class methods as explained in this second answer , but I really need to instantiate MySerial , thus create an instance using __init__ . 我已经尝试了上述实例方法并使用了第二个答案中解释的类方法,但我确实需要实例化MySerial ,因此使用__init__创建一个实例。

How can this be accomplished? 如何实现这一目标? Is it impossible? 这不可能吗?

  • Create a decorator that is an instance method. 创建一个作为实例方法的装饰器。
  • Use this decorator within another class 在另一个类中使用此装饰器

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()

You can use a staticmethod to wrap decorator . 您可以使用staticmethod来包装decorator The inner func_wrap function of decorator contains an additional parameter in its signature: cls . decorator的内部func_wrap函数在其签名中包含一个附加参数: cls cls can be used to access the ser attribute of the instance of App , and then the desired methods write and read can be called from cls.ser . cls可用于访问App实例的ser属性,然后可以从cls.ser调用所需的writeread方法。 Also, note that in your declarations, MySerial.write takes no paramters, but is passed the result of the wrapped function. 另请注意,在声明中, MySerial.write接受参数,但会传递包装函数的结果。 The code below uses *args to prevent the TypeError which would otherwise be raised: 下面的代码使用*args来防止否则会引发的TypeError

class MySerial():
   def __init__(self):
     pass # I have to have an __init__
   def write(self, *args):
     pass # write to buffer
   def read(self):
     pass # read to buffer
   @staticmethod
   def decorator(func):
     def func_wrap(cls, *args, **kwargs):
        cls.ser.write(func(cls, *args, **kwargs))
        return cls.ser.read()
     return func_wrap

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

App().myfunc()

The reason this does not work is because you are refering to self in the class body, where it is not defined. 这不起作用的原因是因为你在类体中引用self ,它没有被定义。 Here are two solutions. 这是两个解决方案。

Store the serial object as class attribute 将序列对象存储为类属性

If you store the MySerial instance as a class attribute, then it sill be accessible in the class body: 如果将MySerial实例存储为属性,则可以在类主体中访问它:

class App():
    ser = MySerial()

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

Decorate on each instantiation 在每个实例化上装饰

Or if you need a different MySerial instance for every App instance, then you will need to wait for the instance to be created to define an instance attribute my_func . 或者,如果您需要为每个App实例使用不同的MySerial实例,则需要等待创建实例以定义实例属性my_func This means the function is decorated dynamically on every instance creation, in which case, the @ decorator syntax must be replaced by a function call. 这意味着函数在每个实例创建时动态修饰,在这种情况下, @ decorator语法必须由函数调用替换。

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

    def myfunc(self):
        return 'yummy_bytes'

This solution generalizes to decorating multiple methods or conditionally deactivating serializing, say in a test environment. 该解决方案通常用于装饰多个方法或有条件地停用序列化,例如在测试环境中。

import env

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

        to_decorate = [] if env.test else ['myfunc']

        for fn_name in to_decorate:
            fn = getattr(self, fn_name)
            setattr(self, fn_name, self.ser.decorator(fn))

There's a lot of hidden pitfalls that make this a risky design, however it is a great learning example. 有许多隐藏的陷阱使这成为一个冒险的设计,但它是一个很好的学习的例子。

First off, the call to 'self' when decorating fails because there is no self at that scope. 首先,装饰时对“自我”的调用失败,因为在该范围内没有自我。 It only exists inside the methods. 它只存在于方法中。 Now that the easy one is out of the way... 现在,简单的一个就不在了......

myfunc is an attribute of App class. myfunc是App类的一个属性。 When you create an instance of App, it is always that one function that gets called. 当您创建App的实例时,始终会调用一个函数。 Even when it becomes methodfied, that only happens once. 即使它变得方法化,也只发生一次。

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 

This is why self is needed to get a unique namespace for the instance. 这就是为什么需要self来为实例获取唯一的命名空间。 It is also why you won't be able to get decorator that is unique to the instance in this way. 这也是为什么你不能以这种方式获得实例独有的装饰器的原因。 Another way to think about it is that Class must be defined before you can produce instances. 考虑它的另一种方法是必须在生成实例之前定义Class。 Therefore, you can't use an instance in the defination of a Class. 因此,您无法在类的定义中使用实例。

Solution

The decorator needs to be written in a way that it won't store any instance attributes. 装饰器需要以不存储任何实例属性的方式编写。 It will access the App instance attributes instead. 它将访问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()

Now I'm assuming MySerial was going to have a unique file, or port or something per instance of App. 现在我假设MySerial将有一个唯一的文件,或端口或每个App实例的东西。 This is what would be recorded in serial_config. 这是将在serial_config中记录的内容。 This may not be elegant if the stream is opening an closing but you should be able to improve this for your exact application. 如果流正在关闭,这可能不是很优雅,但您应该能够针对您的确切应用进行改进。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM