混合抽象方法、类方法和属性装饰器时的奇怪行为

我一直在尝试查看是否可以通过混合三个装饰器来创建抽象类属性(在 Python 3.9.6 中,如果这很重要),我注意到了一些奇怪的行为。

考虑以下代码:

from abc import ABC, abstractmethod

class Foo(ABC):
    @classmethod
    @property
    @abstractmethod
    def x(cls):
        print(cls)
        return None

class Bar(Foo):
    @classmethod
    @property
    def x(cls):
        print("this is executed")
        return super().x

这输出

this is executed
<class '__main__.Bar'>

这意味着不知何故,Bar.x最终会被调用。

PyCharm 警告我Property 'self' cannot be deleted。如果我颠倒@classmethodand的顺序@propertyBar.x则不会调用,但我仍然收到相同的警告,还有另一个警告:(This decorator will not receive a callable it may expect; the built-in decorator returns a special object每当我放在@property上面时也会出现@classmethod)。

删除三个装饰器中的任何一个(进行适当的更改:()在删除时添加@property或在删除时更改cls为)也可以防止被调用。self@classmethodBar.x

我想所有这些都意味着直接混合这些装饰器可能只是一个坏主意(如此处其他线程中关于类属性的讨论所示)。

尽管如此,我很好奇:这里发生了什么?为什么叫 Bar.x?

回答

这看起来像是检查继承的抽象方法的逻辑中的错误。

如果检索其__isabstractmethod__属性产生 ,则类 dict 中的对象被认为是抽象的True。当创建Bar子类时Foo,Python 需要判断是否Bar覆盖了抽象Foo.x,如果覆盖了,覆盖本身是否是抽象的。它应该通过在 MRO 中搜索'x'类 dict 中的条目来完成此操作,因此它可以__isabstractmethod__直接检查描述符而无需调用描述符协议,而是执行简单的Bar.x属性访问。

Bar.x属性访问调用类的属性。它还返回None而不是抽象属性,并且None不是抽象的,因此 Python 对是否Bar.x是抽象感到困惑。Bar.x由于不同的检查,Python 最终仍然认为是抽象的,但是如果您稍微更改示例:

>>> from abc import ABC, abstractmethod
>>> 
>>> class Foo(ABC):
...     @classmethod
...     @property
...     @abstractmethod
...     def x(cls):
...         print(cls)
...         return None
... 
>>> class Bar(Foo): pass
... 
<class '__main__.Bar'>
>>> Bar()
<__main__.Bar object at 0x7f46eca8ab80>

Python 最终认为Bar是一个具体的类,即使更改后的示例根本没有覆盖x


以上是混合抽象方法、类方法和属性装饰器时的奇怪行为的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>