首页 > 解决方案 > @classmethod 没有调用我的自定义描述符的 __get__

问题描述

我有一个名为的装饰器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.qux.special可以知道它是从 a 调用的classmethod吗?或者其他解决这个问题的方法?

标签: pythonpython-decoratorsclass-method

解决方案


classmethod是返回绑定方法的描述符。它不会__get__在此过程中调用您的方法,因为它不能在不破坏描述符协议的某些合同的情况下这样做。(即,instance应该是一个实例,而不是一个类。)所以你的__get__方法没有被调用是完全可以预料的。

那么如何让它发挥作用呢?好吧,想一想:你想要两者some_instance.barSomeClass.bar返回一个Special实例。为了实现这一点,您只需最后@Special应用装饰器:

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))

现在你所有的断言都会通过。


推荐阅读