使用@abstractproperty和@abstractmethod实现/重写的实际区别

6 投票
1 回答
2070 浏览
提问于 2025-04-17 13:18

想象一下,有一个抽象的基类,这个类里面有一个函数,你希望每个后来的子类都能重新定义这个函数。使用abc模块和ABCMeta后,使用@abstractproperty或者@abstractmethod装饰器,真的能强制子类的开发者去实现这个装饰器指定的函数类型吗?根据我的实验,你可以在子类中用方法来覆盖一个抽象属性,也可以用属性来覆盖一个抽象方法。

这个想法是错的吗?

1 个回答

7

这个观点是对的;ABCMeta 的代码并不会区分 abstractpropertyabstractmethod

这两个装饰器都会给被装饰的项目添加一个属性,叫做 .__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 中的非抽象方法或属性匹配。

撰写回答