代码之家  ›  专栏  ›  技术社区  ›  Jordan Lee

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

  •  7
  • Jordan Lee  · 技术社区  · 7 年前

    我正在尝试创建一个类( MySerial )它实例化了一个串行对象,这样我就可以写入/读取串行设备(UART)。有一个实例方法是一个装饰器,它围绕着一个属于完全不同类的函数( App ).所以decorator负责写入和读取串行缓冲区。

    如果我创建一个 我的家族 里面 应用程序 类,我不能使用从中创建的decorator实例方法 我的家族 . 我已经尝试过前面的实例方法和使用类方法,如中所述 this second answer ,但我真的需要实例化 我的家族 ,从而使用 __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 回复  |  直到 7 年前
        1
  •  2
  •   Ajax1234    7 年前

    你可以用 staticmethod 包裹 decorator .内心 func_wrap 功能 室内装修设计师 在其签名中包含一个附加参数: cls . cls公司 可用于访问 ser 实例的属性 App ,然后是所需的方法 write read 可以从 cls.ser .另外,请注意,在您的声明中, MySerial.write 不接受任何参数,但传递包装函数的结果。下面的代码使用 *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()
    
        2
  •  1
  •   Olivier Melançon iacob    7 年前

    这不起作用的原因是因为你指的是 self 在未定义的类主体中。这里有两个解决方案。

    将串行对象存储为类属性

    如果你储存 MySerial 作为一个例子 属性,则可以在类主体中访问它:

    class App():
        ser = MySerial()
    
        @ser.decorator
        def myfunc(self):
            return 'yummy_bytes'
    

    在每个实例上进行装饰

    或者如果你需要一个不同的 我的家族 每一个例子 App 实例,则需要等待创建实例来定义 例子 属性 my_func 。这意味着在每次创建实例时都会动态地修饰函数,在这种情况下 @ 修饰符语法必须替换为函数调用。

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

    这个解决方案推广到修饰多个方法或有条件地停用序列化,比如在测试环境中。

    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))
    
        3
  •  0
  •   Guy Gangemi    7 年前

    有很多隐藏的陷阱,使这成为一个危险的设计,但它是一个很好的学习例子。

    首先,装饰时调用“self”失败,因为在该范围内没有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为实例获取唯一的名称空间。这也是为什么您无法通过这种方式获得实例特有的decorator。 另一种考虑方法是,必须先定义类,然后才能生成实例。因此,在定义类时不能使用实例。

    解决方案

    decorator需要以一种不会存储任何实例属性的方式编写。它将访问应用程序实例属性。

    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在每个应用实例中都会有一个唯一的文件、端口或其他东西。这是串行配置中记录的内容。如果流开始关闭,这可能并不优雅,但您应该能够为您的确切应用程序改进这一点。