使用@abstractproperty和@abstractmethod实现/重写的实际区别
想象一下,有一个抽象的基类,这个类里面有一个函数,你希望每个后来的子类都能重新定义这个函数。使用abc模块和ABCMeta后,使用@abstractproperty
或者@abstractmethod
装饰器,真的能强制子类的开发者去实现这个装饰器指定的函数类型吗?根据我的实验,你可以在子类中用方法来覆盖一个抽象属性,也可以用属性来覆盖一个抽象方法。
这个想法是错的吗?
1 个回答
7
这个观点是对的;ABCMeta
的代码并不会区分 abstractproperty
和 abstractmethod
。
这两个装饰器都会给被装饰的项目添加一个属性,叫做 .__isabstractmethod__
,而 ABCMeta
会利用这个属性来给你定义的抽象类(ABC)添加一个 .__abstractmethods__
属性(这是一个 frozenset
)。然后,object
类型会防止你创建任何类的实例,只要这个类中有任何在 .__abstractmethods__
列表中的名字没有具体的实现。这里并不会区分函数和属性。
举个例子:
>>> from abc import *
>>> class C:
... __metaclass__ = ABCMeta
... @abstractmethod
... def abstract_method(self): pass
... @abstractproperty
... def abstract_property(self): return 'foo'
...
>>> C.__abstractmethods__
frozenset(['abstract_method', 'abstract_property'])
通过在子类中创建这些方法或属性的新实现,ABCMeta
类会找到更少带有 .__isabstractmethod__
属性的方法或属性,这样最终的 __abstractmethods__
集合就会变小;一旦这个集合为空,你就可以创建这个子类的实例了。
这些检查是在 ABCMeta.__new__
构造函数 中进行的,并且不会检查描述符的类型:
cls = super(ABCMeta, mcls).__new__(mcls, name, bases, namespace)
# Compute set of abstract method names
abstracts = set(name
for name, value in namespace.items()
if getattr(value, "__isabstractmethod__", False))
for base in bases:
for name in getattr(base, "__abstractmethods__", set()):
value = getattr(cls, name, None)
if getattr(value, "__isabstractmethod__", False):
abstracts.add(name)
cls.__abstractmethods__ = frozenset(abstracts)
你需要创建一个 ABCMeta
的子类,重写 __new__
方法,并检查任何在基类中声明的抽象方法或属性是否确实与 cls
中的非抽象方法或属性匹配。