在另一个 class 中使用实例方法作为装饰器
Use an instance method as a decorator within another class
我正在尝试创建一个实例化串行对象的 class (MySerial
),以便我可以 write/read 到串行设备 (UART)。有一个实例方法是一个装饰器,它包装了一个属于完全不同的 class (App
) 的函数。所以装饰器负责写入和读取串行缓冲区。
如果我在 App
class 中创建 MySerial
的实例,我将无法使用从 MySerial
创建的装饰器实例方法。
我已经尝试过上述实例方法并使用 class 方法,如 this second answer 中所述,但我确实需要实例化 MySerial
,因此使用 __init__
.[=20= 创建实例]
如何实现?不可能吗?
- 创建一个作为实例方法的装饰器。
- 在另一个 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()
您可以使用 staticmethod
来包裹 decorator
。 decorator
的内部 func_wrap
函数在其签名中包含一个附加参数:cls
。 cls
可以用来访问 App
实例的 ser
属性,然后可以从 [=21] 调用所需的方法 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()
这不起作用的原因是因为您在 class 正文中引用 self
,但未定义它。这里有两个解决方案。
将串行对象存储为 class 属性
如果您将 MySerial
实例存储为 class 属性,那么它仍然可以在 class 正文中访问:
class App():
ser = MySerial()
@ser.decorator
def myfunc(self):
return 'yummy_bytes'
在每个实例化时装饰
或者如果每个 App
实例都需要一个不同的 MySerial
实例,那么您将需要等待创建实例以定义 实例 属性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))
有很多隐藏的陷阱使这个设计具有风险,但它是一个很好的学习示例。
首先,装饰时对 'self' 的调用失败,因为在该范围内没有 self。它只存在于方法内部。现在简单的已经不在了...
myfunc 是 App class 的一个属性。当你创建 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 才能生成实例。因此,您不能在 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 中要记录的内容。如果流正在打开关闭,这可能不太优雅,但您应该能够针对您的具体应用改进它。
我正在尝试创建一个实例化串行对象的 class (MySerial
),以便我可以 write/read 到串行设备 (UART)。有一个实例方法是一个装饰器,它包装了一个属于完全不同的 class (App
) 的函数。所以装饰器负责写入和读取串行缓冲区。
如果我在 App
class 中创建 MySerial
的实例,我将无法使用从 MySerial
创建的装饰器实例方法。
我已经尝试过上述实例方法并使用 class 方法,如 this second answer 中所述,但我确实需要实例化 MySerial
,因此使用 __init__
.[=20= 创建实例]
如何实现?不可能吗?
- 创建一个作为实例方法的装饰器。
- 在另一个 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()
您可以使用 staticmethod
来包裹 decorator
。 decorator
的内部 func_wrap
函数在其签名中包含一个附加参数:cls
。 cls
可以用来访问 App
实例的 ser
属性,然后可以从 [=21] 调用所需的方法 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()
这不起作用的原因是因为您在 class 正文中引用 self
,但未定义它。这里有两个解决方案。
将串行对象存储为 class 属性
如果您将 MySerial
实例存储为 class 属性,那么它仍然可以在 class 正文中访问:
class App():
ser = MySerial()
@ser.decorator
def myfunc(self):
return 'yummy_bytes'
在每个实例化时装饰
或者如果每个 App
实例都需要一个不同的 MySerial
实例,那么您将需要等待创建实例以定义 实例 属性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))
有很多隐藏的陷阱使这个设计具有风险,但它是一个很好的学习示例。
首先,装饰时对 'self' 的调用失败,因为在该范围内没有 self。它只存在于方法内部。现在简单的已经不在了...
myfunc 是 App class 的一个属性。当你创建 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 才能生成实例。因此,您不能在 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 中要记录的内容。如果流正在打开关闭,这可能不太优雅,但您应该能够针对您的具体应用改进它。