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

标签 python python-decorators class-method

我有一个装饰器叫做 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().bar正在调用 __get__ , 它绑定(bind)底层函数并将绑定(bind)方法传递给 Special 的新实例- 这就是为什么 Foo().bar()Foo().bar.special()工作。

  • Foo.baz只是返回原来的 Special实例 - 常规调用和特殊调用都很简单。

  • Foo.qux无需调用我的 __get__ 即可绑定(bind).

    • 新的绑定(bind)对象知道在被直接调用时将类作为第一个参数传递 - 所以 Foo.qux()有效。
    • Foo.qux.special只是调用 .special底层函数(classmethod 不知道如何绑定(bind)它)- 所以 Foo.qux.special()正在调用未绑定(bind)函数,因此 TypeError .

Foo.qux.special有什么办法吗?知道它是从 classmethod 调用的?或者解决这个问题的其他方法?

最佳答案

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

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

class Foo:
    @Special
    @staticmethod
    def baz():
        return 'baz'

    @Special
    @classmethod
    def qux(cls):
        return 'qux'

这使您可以完全控制是否/何时/如何调用装饰函数的描述符协议(protocol)。现在您只需要删除 if instance is None: 方法中的 __get__ 特例,因为它会阻止类方法正常工作。 (原因是 classmethod 对象是不可调用的;你必须调用描述符协议(protocol)将 classmethod 对象变成一个可以调用的函数。)换句话说,Special.__get__ 方法必须无条件调用修饰函数的 __get__ 方法,如下所示:

def __get__(self, instance=None, owner=None):
    return Special(self.func.__get__(instance, owner))

现在您所有的断言都将通过。

关于python - @classmethod 没有调用我的自定义描述符的 __get__,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50847358/

相关文章:

python - 以任意维数对 numpy 数组进行二次采样

Python - 装饰器调用内部函数?

python - Bottle 中的 DRY 验证?

JavaScript : onclick ="class method" => possible?

c# - C# 的 Python 风格类方法?

ruby-on-rails - 如何在 Rails 的同一模型中调用自类方法?

python - 我将什么类型的数据传递到 Django Model.save() 方法中?

python - 我想将字符串“random.randint(2,4), 转换为整数

python 惯用的 python for 循环 if else 语句

python - @staticmethod 和@classmethod 的区别