我有一个装饰器叫做 Special
将函数转换为自身的两个版本:一个可以直接调用并在结果前加上前缀 'regular '
和一个可以用 .special
调用的并在结果前加上 'special '
前缀:
class Special:
def __init__(self, func):
self.func = func
def __get__(self, instance, owner=None):
if instance is None:
return self
return Special(self.func.__get__(instance, owner))
def special(self, *args, **kwargs):
return 'special ' + self.func(*args, **kwargs)
def __call__(self, *args, **kwargs):
return 'regular ' + self.func(*args, **kwargs)
它适用于常规方法和静态方法 - 但 .special
不适用于类方法:
class Foo:
@Special
def bar(self):
return 'bar'
@staticmethod
@Special
def baz():
return 'baz'
@classmethod
@Special
def qux(cls):
return 'qux'
assert Foo().bar() == 'regular bar'
assert Foo().bar.special() == 'special bar'
assert Foo.baz() == 'regular baz'
assert Foo.baz.special() == 'special baz'
assert Foo.qux() == 'regular qux'
assert Foo.qux.special() == 'special qux' # TypeError: qux() missing 1 required positional argument: 'cls'
Foo().bar
正在调用__get__
, 它绑定(bind)底层函数并将绑定(bind)方法传递给Special
的新实例- 这就是为什么Foo().bar()
和Foo().bar.special()
工作。Foo.baz
只是返回原来的Special
实例 - 常规调用和特殊调用都很简单。Foo.qux
无需调用我的__get__
即可绑定(bind).- 新的绑定(bind)对象知道在被直接调用时将类作为第一个参数传递 - 所以
Foo.qux()
有效。 -
Foo.qux.special
只是调用.special
底层函数(classmethod
不知道如何绑定(bind)它)- 所以Foo.qux.special()
正在调用未绑定(bind)函数,因此TypeError
.
- 新的绑定(bind)对象知道在被直接调用时将类作为第一个参数传递 - 所以
Foo.qux.special
有什么办法吗?知道它是从 classmethod
调用的?或者解决这个问题的其他方法?
最佳答案
classmethod
是一个返回绑定(bind)方法的描述符。它不会在此过程中调用您的 __get__
方法,因为它不能在不破坏描述符协议(protocol)的某些约定的情况下这样做。 (也就是说,instance
应该是一个实例,而不是一个类。)所以您的 __get__
方法没有被调用是完全可以预料的。
那么如何让它发挥作用呢?好吧,想一想:您希望 some_instance.bar
和 SomeClass.bar
都返回一个 Special
实例。为了实现这一点,您只需应用 @Special
装饰器 last:
class Foo:
@Special
@staticmethod
def baz():
return 'baz'
@Special
@classmethod
def qux(cls):
return 'qux'
这使您可以完全控制是否/何时/如何调用装饰函数的描述符协议(protocol)。现在您只需要删除 if instance is None:
方法中的 __get__
特例,因为它会阻止类方法正常工作。 (原因是 classmethod 对象是不可调用的;你必须调用描述符协议(protocol)将 classmethod 对象变成一个可以调用的函数。)换句话说,Special.__get__
方法必须无条件调用修饰函数的 __get__
方法,如下所示:
def __get__(self, instance=None, owner=None):
return Special(self.func.__get__(instance, owner))
现在您所有的断言都将通过。
关于python - @classmethod 没有调用我的自定义描述符的 __get__,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50847358/