我有一个名为Special
的装饰器,它将一个函数转换为自身的两个版本:一个可以直接调用,结果前缀为'regular '
,一个可以用{{1}调用并使用.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__
的新实例 - 这就是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
调用它?或者其他一些解决这个问题的方法?
答案 0 :(得分:4)
classmethod
是一个返回绑定方法的描述符。它不会在此过程中调用您的__get__
方法,因为如果不破坏描述符协议的某些协定,它就无法执行此操作。 (即,instance
应该是一个实例,而不是一个类。)因此,未完全预期未调用__get__
方法。
那么你如何让它发挥作用?好吧,想一想:您希望some_instance.bar
和SomeClass.bar
都返回Special
个实例。为了实现这一目标,您只需应用@Special
decorator last :
class Foo:
@Special
@staticmethod
def baz():
return 'baz'
@Special
@classmethod
def qux(cls):
return 'qux'
这使您可以完全控制是否/何时/如何调用修饰函数的描述符协议。现在,您只需要删除if instance is None:
方法中的__get__
特殊情况,因为它可以防止类方法正常工作。 (原因是classmethod对象不可调用;您必须调用描述符协议将classmethod对象转换为可以调用的函数。)换句话说,Special.__get__
方法必须无条件地调用装饰函数的__get__
方法,如下所示:
def __get__(self, instance=None, owner=None):
return Special(self.func.__get__(instance, owner))
现在你所有的断言都会过去。
答案 1 :(得分:3)
问题是classmethod.__get__
没有调用包装函数的__get__
- 因为这基本上是@classmethod
的全部要点。您可以查看the Descriptors HOWTO中的classmethod
等效的纯Python,或funcobject.c
中的实际CPython C源代码,但是您会看到实际上没有办法解决这个问题。
当然,如果你只是@Special
一个@classmethod
,而不是相反,当在一个实例上调用时,一切都会正常工作:
class Foo:
@Special
@classmethod
def spam(cls):
return 'spam'
assert Foo().spam() == 'regular spam'
assert Foo().spam.special() == 'special spam'
...但是现在在课堂上调用时它不起作用:
assert Foo.spam() == 'regular spam'
assert Foo.spam.special() == 'special spam'
...因为你试图调用一个不可调用的classmethod
对象。
但与前一个问题不同,这个问题是可以修复的。事实上,失败的唯一原因是这一部分:
if instance is None:
return self
当您尝试将Special
实例绑定到类时,它只返回self
而不是绑定其包装对象。这意味着它最终只是一个classmethod
对象的包装器而不是绑定类方法的包装器,当然你不能调用classmethod
对象。
但是如果你把它留下来,它会让底层classmethod
绑定方式与正常函数绑定方式相同,这完全是正确的,现在一切正常:
class Special:
def __init__(self, func):
self.func = func
def __get__(self, instance, owner=None):
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)
class Foo:
@Special
def bar(self):
return 'bar'
@Special
@staticmethod
def baz():
return 'baz'
@Special
@classmethod
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().baz() == 'regular baz'
assert Foo().baz.special() == 'special baz'
assert Foo.qux() == 'regular qux'
assert Foo.qux.special() == 'special qux'
assert Foo().qux() == 'regular qux'
assert Foo().qux.special() == 'special qux'
当然这会导致在Python 2.7中包装未绑定的方法对象时出现问题,但我认为你的设计已经在2.7中的常规方法中断了,希望你无论如何也只关心3.x.