@classmethod没有调用我的自定义描述符的__get__

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吗?还是其他解决此问题的方法?

Ara*_*Fey 5

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

现在,您所有的主张都将通过。