在 Python 3 中创建抽象属性会导致 AttributeError

问题描述 投票:0回答:2

如何在Python中创建抽象属性?

import abc
class MyClass(abc.ABC):
    @abc.abstractmethod
    @property
    def foo(self):
        pass

导致错误

AttributeError: attribute '__isabstractmethod__' of 'property' objects is not writable

python python-3.x abstract python-decorators
2个回答
15
投票

事实证明,对于 Python 装饰器来说,顺序很重要。

@abc.abstractmethod
@property

不一样
@property
@abc.abstractmethod

创建抽象属性的正确方法是:

import abc
class MyClass(abc.ABC):
    @property
    @abc.abstractmethod
    def foo(self):
        pass

0
投票

TL;博士:

cowlinator 是正确的,订单很重要。

正式:

根据抽象基类ABC的正式文档

当abstractmethod()与其他方法结合应用时 描述符,它应该被应用为最里面的装饰器,如图所示 在以下使用示例中:...

(粗体是我强调的:不仅排序很重要,而且

abstractmethod
必须是最里面的

不太令人满意的解释:

@property
@abstractmethod
这样的装饰器是包装它们所装饰的函数(方法)的函数。因此,两个装饰器会导致嵌套包装,其中最接近函数定义的一个(编辑器中最低的)是最里面的包装函数。

@property
装饰器中,
__isabstractmethod__
内部属性被声明为属性本身,只有 getter,没有 setter - 使其只读。这是来自
property
builtins.py

的 python 代码
    __isabstractmethod__ = property(lambda self: object(), lambda self, v: None, lambda self: None)  # default

第一个参数是 getter,第二个参数是 setter - 在本例中没有 - 如果您不相信我,请参阅此处的构造函数:

   def __init__(self, fget=None, fset=None, fdel=None, doc=None): # known special case of property.__init__

@abstractmethod
中,包装
__isabstractmethod__
属性是 set
True
。请看这里:

    funcobj.__isabstractmethod__ = True

来自

abc
模块,其中
funcobj
是被修饰的方法。现在,通常这就是您想要的抽象方法,但在本例中,它是包装原始方法的 属性装饰器函数 。正如我们在上面看到的,该函数使
__isabstractmethod__
成为只读。

我说“不太令人满意”是因为没有适当的理由——只有晦涩的文档,直到遇到问题时你才会找到它们。我猜这实际上只是装饰器工作方式的限制。

对于额外的背景,我在 GitHub 中跟踪了这个更改(一定喜欢开源!),其中包括对这些发行说明的更新,其中声明 - 也没有太多解释:

abc.abstractproperty 已被弃用,使用属性 改为 abc.abstractmethod() 。

对于您获得的异常的深层源代码,您可以在此处查看

python描述符的C源代码并向下搜索“不可写”以查看它在尝试生成setter时在何处抛出异常。

© www.soinside.com 2019 - 2024. All rights reserved.