如何在 python 抽象类中创建抽象属性

Posted

技术标签:

【中文标题】如何在 python 抽象类中创建抽象属性【英文标题】:How to create abstract properties in python abstract classes 【发布时间】:2011-08-23 01:15:27 【问题描述】:

在下面的代码中,我创建了一个基本抽象类Base。我希望所有继承自Base 的类都提供name 属性,所以我将此属性设为@abstractmethod

然后我创建了Base 的一个子类,称为Base_1,它旨在提供一些功能,但仍然保持抽象。在Base_1 中没有name 属性,但是python 使该类的对象不存在错误。如何创建抽象属性?

from abc import ABCMeta, abstractmethod

class Base(object):
    __metaclass__ = ABCMeta
    def __init__(self, strDirConfig):
        self.strDirConfig = strDirConfig
    
    @abstractmethod
    def _doStuff(self, signals):
        pass
    
    @property    
    @abstractmethod
    def name(self):
        # this property will be supplied by the inheriting classes
        # individually
        pass
    

class Base_1(Base):
    __metaclass__ = ABCMeta
    # this class does not provide the name property, should raise an error
    def __init__(self, strDirConfig):
        super(Base_1, self).__init__(strDirConfig)
    
    def _doStuff(self, signals):
        print 'Base_1 does stuff'
        

class C(Base_1):
    @property
    def name(self):
        return 'class C'
    
        
if __name__ == '__main__':
    b1 = Base_1('abc')  

【问题讨论】:

陷阱:如果您忘记在class C 中使用装饰器@propertyname 将恢复为方法。 【参考方案1】:

在Python 3.3 之前,您不能嵌套@abstractmethod@property

使用@abstractproperty 创建抽象属性 (docs)。

from abc import ABCMeta, abstractmethod, abstractproperty

class Base(object):
    # ...
    @abstractproperty
    def name(self):
        pass

代码现在引发了正确的异常:

回溯(最近一次通话最后): 文件“foo.py”,第 36 行,在 b1 = Base_1('abc') TypeError:无法使用抽象方法名称实例化抽象类 Base_1

【讨论】:

实际上这个答案对于年轻的蟒蛇来说是错误的:从 3.3 开始,@abstractproperty 已被弃用,取而代之的是像 OP 这样的组合。 来自 3.3 文档:docs.python.org/3/library/abc.html#abc.abstractproperty @abstractproperty in the Python 2 docs 所以直到 3.3,只需 raise NotImplementedError 我们可以从object继承并仍然使用ABC注解吗?它会像示例中所示那样工作吗?【参考方案2】:

由于Python 3.3 修复了一个错误,这意味着property() 装饰器现在在应用于抽象方法时被正确识别为抽象。

注意:订单很重要,您必须在@abstractmethod上方使用@property

Python 3.3+: (python docs):

from abc import ABC, abstractmethod

class C(ABC):
    @property
    @abstractmethod
    def my_abstract_property(self):
        ...

Python 2: (python docs)

from abc import ABC, abstractproperty

class C(ABC):
    @abstractproperty
    def my_abstract_property(self):
        ...

【讨论】:

@James 如何使它与 python 2 兼容? @James 实际上我的意思是两者兼而有之,但没关系我根据您的解决方案发布了答案 我认为python不会检查实现是否真的有@property装饰器,它只是检查一个名为my_abstract_property的方法是否已创建。【参考方案3】:

根据上面詹姆斯的回答

def compatibleabstractproperty(func):

    if sys.version_info > (3, 3):             
        return property(abstractmethod(func))
    else:
        return abstractproperty(func)

并将其用作装饰器

@compatibleabstractproperty
def env(self):
    raise NotImplementedError()

【讨论】:

【参考方案4】:

如果您希望所需的实例级属性也使用属性装饰器,则在抽象类中使用@property 装饰器(如answer by James 中所推荐的那样)会起作用。

如果不想使用属性装饰器,可以使用super()。我最终使用了来自数据类的__post_init__() 之类的东西,它获得了实例级属性所需的功能:

import abc
from typing import List

class Abstract(abc.ABC):
    """An ABC with required attributes.

    Attributes:
        attr0
        attr1 
    """

    @abc.abstractmethod
    def __init__(self):
        """Forces you to implement __init__ in 'Concrete'. 
        Make sure to call __post_init__() from inside 'Concrete'."""

    def __post_init__(self):
        self._has_required_attributes()
        # You can also type check here if you want.

    def _has_required_attributes(self):
        req_attrs: List[str] = ['attr0', 'attr1']
        for attr in req_attrs:
            if not hasattr(self, attr):
                raise AttributeError(f"Missing attribute: 'attr'")

class Concrete(Abstract):

    def __init__(self, attr0, attr1):
        self.attr0 = attr0
        self.attr1 = attr1
        self.attr2 = "some value" # not required
        super().__post_init__() # Enforces the attribute requirement.

【讨论】:

以上是关于如何在 python 抽象类中创建抽象属性的主要内容,如果未能解决你的问题,请参考以下文章

Python:在类中创建抽象静态属性

是否可以在 Python 中创建抽象类?

c ++如何在原始抽象类中创建一个接受派生类输入的函数

如何在 Python 中创建一个行为类似于 Django 抽象基类的类?

在 C++ 中创建派生抽象类的实例

Java:在抽象类中创建获取扩展类名称的方法