问题描述
我一直在尝试查看是否可以通过混合三个装饰器来创建抽象类属性(在 Python 3.9.6 中,如果这很重要),我注意到了一些奇怪的行为.
考虑以下代码:
from abc import ABC,abstractmethod类 Foo(ABC):@类方法@财产@抽象方法定义 x(cls):打印(CLS)返回无类酒吧(Foo):@类方法@财产定义 x(cls):打印(这是执行")返回 super().x
这个输出
这个被执行了<class '__main__.Bar'>
这意味着不知何故,Bar.x
最终会被调用.
PyCharm 警告我无法删除属性self"
.如果我颠倒 @classmethod
和 @property
的顺序,则不会调用 Bar.x
,但我仍然收到相同的警告,并且另一个:这个装饰器不会收到它可能期望的可调用对象;内置装饰器返回一个特殊对象
(每当我将 @property
放在 @classmethod
上方时,它也会出现).
删除三个装饰器中的任何一个(进行适当的更改:在删除 @property
或将 cls
更改为 时添加
在删除 ()
self@classmethod
时也会阻止 Bar.x
被调用.
我想所有这些都意味着直接混合这些装饰器可能只是一个坏主意(正如此处其他线程中关于类属性的讨论所表明的那样).
不过,我很好奇:这里发生了什么?为什么叫 Bar.x?
这看起来像是检查继承的抽象方法的逻辑中的错误.
如果检索其 __isabstractmethod__
属性产生 True
,则类 dict 中的对象被认为是抽象的.当Bar
子类Foo
时,Python需要判断Bar
是否覆盖了抽象的Foo.x
,如果是,覆盖本身是否是抽象的.它应该通过在 MRO 中搜索类字典中的 'x'
条目来做到这一点,这样它就可以直接检查描述符上的 __isabstractmethod__
而无需调用描述符协议,而是执行一个简单的Bar.x
属性访问.
Bar.x
属性访问调用类属性.它还返回 None
而不是抽象属性,并且 None
不是抽象的,所以 Python 会混淆 Bar.x
是否是抽象的.由于不同的检查,Python 最终仍然认为 Bar.x
是抽象的,但是如果您稍微更改示例:
Python 最终认为 Bar
是一个具体的类,即使更改后的示例根本没有覆盖 x
.
I've been trying to see whether one can create an abstract class property by mixing the three decorators (in Python 3.9.6, if that matters), and I noticed some strange behaviour.
Consider the following code:
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 outputs
this is executed
<class '__main__.Bar'>
This means that somehow, Bar.x
ends up being called.
PyCharm warns me that Property 'self' cannot be deleted
. If I reverse the order of @classmethod
and @property
, Bar.x
is not called, but I still get the same warning, and also another one: This decorator will not receive a callable it may expect; the built-in decorator returns a special object
(this also appears whenever I put @property
above @classmethod
).
Removing any of the three decorators (with the appropriate changes: adding ()
when removing @property
or changing cls
to self
when removing @classmethod
) also prevents Bar.x
from being called.
I suppose all of this means that it's probably just a bad idea to directly mix those decorators (as indicated by discussion about class properties in other threads here).
Neverthless, I am curious: what is happening here? Why is Bar.x called?
This looks like a bug in the logic that checks for inherited abstract methods.
An object in a class dict is considered abstract if retrieving its __isabstractmethod__
attribute produces True
. When Bar
subclasses Foo
, Python needs to determine whether Bar
overrides the abstract Foo.x
, and if so, whether the override is itself abstract. It should do this by searching the MRO for an 'x'
entry in a class dict, so it can examine __isabstractmethod__
on descriptors directly without invoking the descriptor protocol, but instead, it performs a simple Bar.x
attribute access.
The Bar.x
attribute access invokes the class property. It also returns None
instead of the abstract property, and None
isn't abstract, so Python gets confused about whether Bar.x
is abstract. Python ends up still thinking Bar.x
is abstract due to a different check, but if you change the example a bit:
>>> 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 ends up thinking Bar
is a concrete class, even though the changed example doesn't override x
at all.
这篇关于混合抽象方法、类方法和属性装饰器时的奇怪行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!