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

然后我创建了Base的子类Base_1,它提供了一些功能,但仍然是抽象的。Base_1中没有name属性,但是python在没有错误的情况下创建了该类的对象。如何创建抽象属性?

from abc import ABCMeta, abstractmethod

class Base(object):
# class Base(metaclass = ABCMeta): <- Python 3
    __metaclass__ = ABCMeta
    def __init__(self, str_dir_config):
        self.str_dir_config = str_dir_config
    
    @abstractmethod
    def _do_stuff(self, signals):
        pass
    
    @property    
    @abstractmethod
    def name(self):
        """This property will be supplied by the inheriting classes
        individually.
        """
        pass
    

class Base1(Base):
    __metaclass__ = ABCMeta
    """This class does not provide the name property and should
    raise an error.
    """
    def __init__(self, str_dir_config):
        super(Base1, self).__init__(str_dir_config)
        # super().__init__(str_dir_config) <- Python 3
    
    def _do_stuff(self, signals):
        print "Base_1 does stuff"
        # print("Base_1 does stuff") <- Python 3

class C(Base1):
    @property
    def name(self):
        return "class C"
    

if __name__ == "__main__":
    b1 = Base1("abc")

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

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

from abc import ABCMeta, abstractmethod, abstractproperty

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

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

Traceback (most recent call last):
  File "foo.py", line 36, in 
    b1 = Base_1('abc')  
TypeError: Can't instantiate abstract class Base_1 with abstract methods name

自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文档)

from abc import ABC, abstractproperty

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

根据James的回答

def compatibleabstractproperty(func):

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

把它当装饰用

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

如果您希望所需的实例级属性也使用属性装饰器,那么在抽象类中使用@property装饰器(正如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 3.6+中,您也可以在不提供默认值的情况下注释变量。我发现这是一种更简洁的抽象方法。

class Base():
    name: str
    
    def print_name(self):
        print(self.name)  # will raise an Attribute error at runtime if `name` isn't defined in subclass

class Base_1(Base):
    name = "base one"

它也可以用来强制你在__new__或__init__方法中初始化变量

作为另一个例子,当您尝试初始化Base_1类时,下面的代码将失败

    class Base():
        name: str

        def __init__(self):
            self.print_name()

    class Base_1(Base):
        _nemo = "base one"
    
    b = Base_1() 

AttributeError: 'Base_1'对象没有name属性


例如,您可以使用@abstractmethod和@property, @name定义抽象的getter、setter和deleter。Person抽象类中的setter或@name.deleter,如下所示。*@abstractmethod必须是最内层的装饰器,否则会出现错误:

from abc import ABC, abstractmethod

class Person(ABC):

    @property
    @abstractmethod # The innermost decorator
    def name(self): # Abstract getter
        pass

    @name.setter
    @abstractmethod # The innermost decorator
    def name(self, name): # Abstract setter
        pass

    @name.deleter
    @abstractmethod # The innermost decorator
    def name(self): # Abstract deleter
        pass

然后,你可以用Student类扩展Person抽象类,覆盖Student类中的抽象getter、setter和deleter,实例化Student类并调用getter、setter和deleter,如下所示:

class Student(Person):

    def __init__(self, name):
        self._name = name
    
    @property
    def name(self): # Overrides abstract getter
        return self._name
    
    @name.setter
    def name(self, name): # Overrides abstract setter
        self._name = name
    
    @name.deleter
    def name(self): # Overrides abstract deleter 
        del self._name

obj = Student("John") # Instantiates "Student" class
print(obj.name) # Getter
obj.name = "Tom" # Setter
print(obj.name) # Getter
del obj.name # Deleter
print(hasattr(obj, "name"))

输出:

John
Tom
False

实际上,即使你不重写Student类中的抽象setter和delete,并实例化Student类,如下所示:

class Student(Person): # Extends "Person" class
    
    def __init__(self, name):
        self._name = name
    
    @property
    def name(self): # Overrides only abstract getter
        return self._name

    # @name.setter
    # def name(self, name): # Overrides abstract setter
    #     self._name = name
    
    # @name.deleter
    # def name(self): # Overrides abstract deleter 
    #     del self._name

obj = Student("John") # Instantiates "Student" class
# ...

没有出现如下错误:

John
Tom
False

但是,如果你不重写学生类中的抽象getter, setter和deleter,并实例化学生类,如下所示:

class Student(Person): # Extends "Person" class
    
    def __init__(self, name):
        self._name = name
    
    # @property
    # def name(self): # Overrides only abstract getter
    #     return self._name

    # @name.setter
    # def name(self, name): # Overrides abstract setter
    #     self._name = name
    
    # @name.deleter
    # def name(self): # Overrides abstract deleter 
    #     del self._name

obj = Student("John") # Instantiates "Student" class
# ...

出现以下错误:

不能用抽象方法名实例化抽象类Student

并且,如果你不重写Student类中的抽象getter,并实例化Student类,如下所示:

class Student(Person): # Extends "Person" class
    
    def __init__(self, name):
        self._name = name
    
    # @property
    # def name(self): # Overrides only abstract getter
    #     return self._name

    @name.setter
    def name(self, name): # Overrides abstract setter
        self._name = name
    
    @name.deleter
    def name(self): # Overrides abstract deleter 
        del self._name

obj = Student("John") # Instantiates "Student" class
# ...

出现以下错误:

没有定义name

并且,如果@abstractmethod不是最里面的装饰器,如下所示:

from abc import ABC, abstractmethod

class Person(ABC):

    @abstractmethod # Not the innermost decorator
    @property
    def name(self): # Abstract getter
        pass

    @name.setter
    @abstractmethod # The innermost decorator
    def name(self, name): # Abstract setter
        pass

    @name.deleter
    @abstractmethod # The innermost decorator
    def name(self): # Abstract deleter
        pass

出现以下错误:

property对象的属性isabstractmethod不可写


另一个可能的解决方案是使用元类。

一个最小的例子是这样的:

class BaseMetaClass(type):
    def __new__(mcls, class_name, bases, attrs):
        required_attrs = ('foo', 'bar')
        for attr in required_attrs:
            if not attr in attrs:
                raise RunTimeError(f"You need to set {attr} in {class_name}")
        return super().__new__(mcls, class_name, bases, attrs)


class Base(metaclass=BaseMeta):
    foo: str
    bar: int

这种方法的一个优点是检查将发生在定义时(而不是实例化时)。

此外,在子类中设置类属性比声明属性要容易一些(只要它们是预先知道的简单值),并且最终的类看起来会更简洁