在下面的代码中,我创建了一个基抽象类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")
另一个可能的解决方案是使用元类。
一个最小的例子是这样的:
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
这种方法的一个优点是检查将发生在定义时(而不是实例化时)。
此外,在子类中设置类属性比声明属性要容易一些(只要它们是预先知道的简单值),并且最终的类看起来会更简洁
在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):
...
另一个可能的解决方案是使用元类。
一个最小的例子是这样的:
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
这种方法的一个优点是检查将发生在定义时(而不是实例化时)。
此外,在子类中设置类属性比声明属性要容易一些(只要它们是预先知道的简单值),并且最终的类看起来会更简洁