我认为以下内容可以用作装饰器
class D:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
return self.func(*args, **kwargs)
class A:
@D
def f(self, x):
pass
a=A()
a.f(1)
但我得到 TypeError: f() missing 1 required positional argument: 'x'
这是怎么回事,有没有办法像这样使用类作为装饰器?
答案 0 :(得分:1)
问题是除了装饰器机制之外,还有 Python 使用的机制,以便类体内的函数表现为实例方法:它是 "descriptor protocol"。这实际上很简单:所有函数对象都有一个 __get__
方法(但不是 __set__
或 __del__
)方法,这使它们成为“非数据描述符”。当 Python 从实例中检索属性时,__get__
会以实例作为参数被调用 - __get__
方法必须返回一个作为方法工作的可调用对象,并且必须知道哪个是实例调用:
# example only - DO NOT DO THIS but for learning purposes,
# due to concurrency problems:
class D:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
return self.func(self.instance, *args, **kwargs)
def __get__(self, instance, owner):
self.instance = instance
return self
class A:
@D
def f(self, x):
print(self, x)
a=A()
a.f(1)
这将打印“<main.A object at 0x...> 1”
但是,由于很容易察觉,这仅允许一次在单个实例中调用装饰方法 - 即使拥有多个“A”实例的非并行代码也可能使用错误的实例调用该方法头脑。也就是说,这个序列:
In [127]: a1 = A()
In [128]: a2 = A()
In [129]: f1 = a1.f
In [130]: f2 = a2.f
In [131]: f1()
最终会调用“a2.f()”而不是“a1.f()”
为了避免这种情况,您必须返回一个来自 __get__
的可调用对象,它不需要将实例作为类属性进行检索。一种方法是创建一个 partial
可调用并包含它 - 但是,请注意,由于这是一个必要步骤,因此装饰器类本身不需要具有“运行包装器 + 原始代码”功能在 __call__
方法中 - 它可以有任何名称:
from functools import partial
class D:
def __init__(self, func):
self.func = func
def __call__(self, *args, _instance=None, **kwargs):
if _instance:
return self.func(_instance, *args, **kwargs)
else:
return self.func(*args, **kwargs)
def __get__(self, instance, owner):
return partial(self.__call__, _instance=instance)
class A:
@D
def f(self, x):
print(self, x)
a=A()
a.f(1)