如何在Python中创建抽象属性?
import abc
class MyClass(abc.ABC):
@abc.abstractmethod
@property
def foo(self):
pass
导致错误
AttributeError: attribute '__isabstractmethod__' of 'property' objects is not writable
事实证明,对于 Python 装饰器来说,顺序很重要。
@abc.abstractmethod
@property
与
不一样@property
@abc.abstractmethod
创建抽象属性的正确方法是:
import abc
class MyClass(abc.ABC):
@property
@abc.abstractmethod
def foo(self):
pass
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时在何处抛出异常。