本文介绍了混合抽象方法、类方法和属性装饰器时的奇怪行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在尝试查看是否可以通过混合三个装饰器来创建抽象类属性(在 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 是抽象的,但是如果您稍微更改示例:

>>>从 abc 导入 ABC,抽象方法>>>>>>类 Foo(ABC):... @classmethod... @财产... @抽象方法... def x(cls):...打印(CLS)...返回无...>>>类 Bar(Foo): 通过...<class '__main__.Bar'>>>>酒吧()<__main__.Bar 对象在 0x7f46eca8ab80>

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.

这篇关于混合抽象方法、类方法和属性装饰器时的奇怪行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-13 13:16