在另一个类中使用实例方法作为装饰器
Posted
技术标签:
【中文标题】在另一个类中使用实例方法作为装饰器【英文标题】:Use an instance method as a decorator within another class 【发布时间】:2018-11-11 01:22:33 【问题描述】:我正在尝试创建一个实例化串行对象的类 (MySerial
),以便我可以写入/读取串行设备 (UART)。有一个实例方法是一个装饰器,它包装了属于完全不同的类 (App
) 的函数。所以装饰器负责对串行缓冲区进行写入和读取。
如果我在App
类中创建MySerial
的实例,我不能使用从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()
【问题讨论】:
只需使用__init__
中的长格式,例如self.myfunc = self.ser.decorator(self.myfunc)
@AChampion 谢谢,但我有 60 多个功能需要处理。我把自己置于一个棘手的境地。
【参考方案1】:
您可以使用staticmethod
包装decorator
。 decorator
的内部 func_wrap
函数在其签名中包含一个附加参数:cls
。通过cls
可以访问App
实例的ser
属性,然后可以从cls.ser
调用所需的方法write
和read
。另外,请注意,在您的声明中,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()
【讨论】:
我以前没见过那个鬼鬼祟祟的cls
参数。我会试一试。并报告回来。【参考方案2】:
这不起作用的原因是因为您在类主体中引用self
,但未定义它。这里有两种解决方案。
将串行对象存储为类属性
如果您将 MySerial
实例存储为 class 属性,那么它仍然可以在类主体中访问:
class App():
ser = MySerial()
@ser.decorator
def myfunc(self):
return 'yummy_bytes'
在每个实例上进行装饰
或者,如果您需要为每个App
实例使用不同的MySerial
实例,那么您将需要等待创建实例来定义一个instance 属性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))
【讨论】:
我实际上已经在实施你的第一个解决方案,但是当我问我的另一个 question 时,我遇到了需要进行单元测试的巨大头痛 我更倾向于您的第二种解决方案,但我有 60 多个功能需要处理。 Martijn Pieters 在那个问题中的评论是一个非常好的观点。也许您可以编写一个新类,它是您的 MySerial 模拟版本,并在每次测试之前用这个模拟实例替换 App.ser? 至于第二种解决方案,我实际上认为为每个应用程序设置不同的序列更有意义。所以我推荐那个。无论如何,您将删除 60 个@decorator
并添加 60 个实例属性。这对您来说确实是个问题,您可以编写一个辅助函数来装饰列表中的函数,但是拥有 60 行 __init__
函数不一定是问题。
@JordanLee 如果您愿意稍等片刻,我也在为您研究元类方法【参考方案3】:
有很多隐藏的陷阱使这个设计成为一个冒险的设计,但它是一个很好的学习示例。
首先,装饰时调用“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 来为实例获取唯一的命名空间。这也是为什么您无法以这种方式获得实例独有的装饰器的原因。 另一种思考方式是必须先定义类,然后才能生成实例。因此,您不能在类的定义中使用实例。
解决方案
装饰器需要以不存储任何实例属性的方式编写。它将改为访问 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 中。如果流打开关闭,这可能不太优雅,但您应该能够针对您的确切应用程序改进它。
【讨论】:
以上是关于在另一个类中使用实例方法作为装饰器的主要内容,如果未能解决你的问题,请参考以下文章