在下面的代码中,我创建了一个基抽象类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.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属性
在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属性
在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):
...
如果您希望所需的实例级属性也使用属性装饰器,那么在抽象类中使用@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.