Ida*_*rye 4 python class-method python-decorators
我有一个装饰器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)
Run Code Online (Sandbox Code Playgroud)
它适用于常规方法和静态方法-但.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'
Run Code Online (Sandbox Code Playgroud)
Foo().bar
被调用__get__
,其结合底层函数并将所结合的方法的一个新实例Special
-这就是为什么既Foo().bar()
和Foo().bar.special()
工作。
Foo.baz
只是返回原始Special
实例-常规调用和特殊调用都很简单。
Foo.qux
绑定而不打电话给我__get__
。
Foo.qux()
可以工作。Foo.qux.special
只是简单地调用.special
基础函数(classmethod
不知道如何绑定它)- Foo.qux.special()
调用一个未绑定函数,因此是TypeError
。有什么方法Foo.qux.special
可以知道它是从调用的classmethod
吗?还是其他解决此问题的方法?
classmethod
是一个返回绑定方法的描述符。它不会__get__
在此过程中调用您的方法,因为在不破坏描述符协议的某些约定的情况下它无法进行调用。(也就是说,instance
应该是一个实例,而不是一个类。)因此,__get__
完全不希望您的方法被调用。
那么如何使它工作呢?嗯,想一想:你想同时some_instance.bar
并SomeClass.bar
返回一个Special
实例。为此,您只需在最后应用@Special
装饰器:
class Foo:
@Special
@staticmethod
def baz():
return 'baz'
@Special
@classmethod
def qux(cls):
return 'qux'
Run Code Online (Sandbox Code Playgroud)
这使您可以完全控制是否/何时/如何调用修饰函数的描述符协议。现在,您只需要删除方法中的if instance is None:
特殊情况__get__
,因为这会阻止类方法正常工作。(原因是类方法对象是不可调用的;您必须调用描述符协议才能将类方法对象转换为可以调用的函数。)换句话说,该Special.__get__
方法必须无条件地调用装饰函数的__get__
方法,如下所示:
def __get__(self, instance=None, owner=None):
return Special(self.func.__get__(instance, owner))
Run Code Online (Sandbox Code Playgroud)
现在,您所有的主张都将通过。