@classmethod 没有调用我的自定义描述符 __get__
@classmethod not invoking my custom descriptor's __get__
我有一个名为 Special
的装饰器,它可以将一个函数转换为自身的两个版本:一个可以直接调用并在结果前加上 'regular '
前缀,另一个可以用 [= 调用15=] 并在结果前加上前缀 '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 方法:
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__
.
即可绑定
- 新绑定对象知道在直接调用时将 class 作为第一个参数传递 - 所以
Foo.qux()
有效。
Foo.qux.special
只是调用底层函数的 .special
(classmethod
不知道如何绑定它) - 所以 Foo.qux.special()
正在调用一个未绑定的函数,因此 TypeError
.
有什么方法可以让 Foo.qux.special
知道它是从 classmethod
调用的吗?或者解决这个问题的其他方法?
classmethod
是一个描述符,return 是一个绑定方法。它不会在此过程中调用您的 __get__
方法,因为它不能在不破坏描述符协议的某些约定的情况下这样做。 (也就是说,instance
应该是一个实例,而不是 class。)所以你的 __get__
方法没有被调用是完全可以预料的。
那么你是如何让它发挥作用的呢?好吧,想想看:你想要 some_instance.bar
和 SomeClass.bar
到 return 一个 Special
实例。为了实现这一点,您只需应用 @Special
装饰器 last:
class Foo:
@Special
@staticmethod
def baz():
return 'baz'
@Special
@classmethod
def qux(cls):
return 'qux'
这使您可以完全控制 if/when/how 调用修饰函数的描述符协议。现在您只需删除 __get__
方法中的 if instance is None:
特殊情况,因为它会阻止 class 方法正常工作。 (原因是class方法对象是不可调用的,必须调用描述符协议将class方法对象变成可以调用的函数。)换句话说,Special.__get__
方法必须无条件调用修饰函数的 __get__
方法,如下所示:
def __get__(self, instance=None, owner=None):
return Special(self.func.__get__(instance, owner))
现在你所有的断言都将通过。
问题是 classmethod.__get__
没有调用包装函数的 __get__
——因为这基本上就是 @classmethod
的全部要点。您可以查看 the Descriptors HOWTO, or the actual CPython C source in funcobject.c
中等同于 classmethod
的纯 Python 以获取详细信息,但您会发现确实没有办法解决这个问题。
当然,如果你只是 @Special
一个 @classmethod
,而不是反过来,在实例上调用时一切都会正常工作:
class Foo:
@Special
@classmethod
def spam(cls):
return 'spam'
assert Foo().spam() == 'regular spam'
assert Foo().spam.special() == 'special spam'
… 但是现在在 class:
上调用时它不会工作
assert Foo.spam() == 'regular spam'
assert Foo.spam.special() == 'special spam'
… 因为你试图调用一个 classmethod
对象,它是不可调用的。
但与上一个问题不同的是,这个问题是可以解决的。事实上,失败的唯一原因是这部分:
if instance is None:
return self
当您尝试将 Special
实例绑定到 class 时,它只是 returns self
而不是绑定其包装对象。这意味着它最终只是 classmethod
对象的包装器,而不是绑定 class 方法的包装器,当然你不能调用 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 .
我有一个名为 Special
的装饰器,它可以将一个函数转换为自身的两个版本:一个可以直接调用并在结果前加上 'regular '
前缀,另一个可以用 [= 调用15=] 并在结果前加上前缀 '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 方法:
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__
.- 新绑定对象知道在直接调用时将 class 作为第一个参数传递 - 所以
Foo.qux()
有效。 Foo.qux.special
只是调用底层函数的.special
(classmethod
不知道如何绑定它) - 所以Foo.qux.special()
正在调用一个未绑定的函数,因此TypeError
.
- 新绑定对象知道在直接调用时将 class 作为第一个参数传递 - 所以
有什么方法可以让 Foo.qux.special
知道它是从 classmethod
调用的吗?或者解决这个问题的其他方法?
classmethod
是一个描述符,return 是一个绑定方法。它不会在此过程中调用您的 __get__
方法,因为它不能在不破坏描述符协议的某些约定的情况下这样做。 (也就是说,instance
应该是一个实例,而不是 class。)所以你的 __get__
方法没有被调用是完全可以预料的。
那么你是如何让它发挥作用的呢?好吧,想想看:你想要 some_instance.bar
和 SomeClass.bar
到 return 一个 Special
实例。为了实现这一点,您只需应用 @Special
装饰器 last:
class Foo:
@Special
@staticmethod
def baz():
return 'baz'
@Special
@classmethod
def qux(cls):
return 'qux'
这使您可以完全控制 if/when/how 调用修饰函数的描述符协议。现在您只需删除 __get__
方法中的 if instance is None:
特殊情况,因为它会阻止 class 方法正常工作。 (原因是class方法对象是不可调用的,必须调用描述符协议将class方法对象变成可以调用的函数。)换句话说,Special.__get__
方法必须无条件调用修饰函数的 __get__
方法,如下所示:
def __get__(self, instance=None, owner=None):
return Special(self.func.__get__(instance, owner))
现在你所有的断言都将通过。
问题是 classmethod.__get__
没有调用包装函数的 __get__
——因为这基本上就是 @classmethod
的全部要点。您可以查看 the Descriptors HOWTO, or the actual CPython C source in funcobject.c
中等同于 classmethod
的纯 Python 以获取详细信息,但您会发现确实没有办法解决这个问题。
当然,如果你只是 @Special
一个 @classmethod
,而不是反过来,在实例上调用时一切都会正常工作:
class Foo:
@Special
@classmethod
def spam(cls):
return 'spam'
assert Foo().spam() == 'regular spam'
assert Foo().spam.special() == 'special spam'
… 但是现在在 class:
上调用时它不会工作assert Foo.spam() == 'regular spam'
assert Foo.spam.special() == 'special spam'
… 因为你试图调用一个 classmethod
对象,它是不可调用的。
但与上一个问题不同的是,这个问题是可以解决的。事实上,失败的唯一原因是这部分:
if instance is None:
return self
当您尝试将 Special
实例绑定到 class 时,它只是 returns self
而不是绑定其包装对象。这意味着它最终只是 classmethod
对象的包装器,而不是绑定 class 方法的包装器,当然你不能调用 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 .