使用抽象基类与普通继承的区别

21

我正在尝试理解使用抽象基类的好处。考虑以下这两个代码片段:

抽象基类:

from abc import ABCMeta, abstractmethod, abstractproperty

class CanFly:
    __metaclass__ = ABCMeta

    @abstractmethod
    def fly(self):
        pass

    @abstractproperty
    def speed(self):
        pass


class Bird(CanFly):

    def __init__(self):
        self.name = 'flappy'

    @property
    def speed(self):
        return 1

    def fly(self):
        print('fly')


b = Bird()

print(isinstance(b, CanFly))  # True
print(issubclass(Bird, CanFly))  #  True

简单继承:

class CanFly(object):

    def fly(self):
        raise NotImplementedError

    @property
    def speed(self):
        raise NotImplementedError()


class Bird(CanFly):

    @property
    def speed(self):
        return 1

    def fly(self):
        print('fly')


b = Bird()

print(isinstance(b, CanFly))  # True
print(issubclass(Bird, CanFly))  # True

正如你所看到的,这两种方法都支持使用 isinstanceissubclass 进行词形变化。

现在,我知道的一个区别是,如果尝试实例化抽象基类的子类而没有覆盖所有抽象方法/属性,则程序将会报错。然而,如果使用带有 NotImplementedError 的普通继承,直到实际调用相关方法/属性时,代码才会失败。

除此之外,使用抽象基类还有什么不同之处吗?


可能是为什么在Python中使用抽象基类?的重复问题。 - Jayanth Koushik
2
@JayanthKoushik 我认为这个问题不是那个问题的重复,因为这个问题询问的是抽象基类与普通继承之间的区别,而另一个问题则询问抽象基类与鸭子类型之间的区别。 - DataMan
1个回答

12
除了你在问题中提到的内容,最值得注意的具体细节是,存在 @abstractmethod@abstractproperty1 装饰器,并且从 ABC 继承(或具有 ABCMeta 元类),这将完全防止你实例化该对象。
from abc import ABC, abstractmethod

class AbsParent(ABC):
    @abstractmethod
    def foo(self):
        pass

AbsParent()

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class AbsParent with abstract methods foo

然而,这里还有更多的内容。抽象基类是在PEP 3119中引入到Python中的。我建议阅读“Rationale”部分,以了解Guido首次引入它们的原因。我的简略总结是,它们更多地关注于哲学问题,而不是具体特征。它们的目的是向外部检查器发出信号,表明对象继承自该ABC,因为它继承自ABC,它将遵循诚信协议。这个“诚信协议”的意思是子对象将遵循父对象的意图。这个协议的实际实现取决于你,这就是为什么它是一个诚信协议,而不是一个明确的合同。
这主要通过register()方法的视角展现出来。任何具有ABCMeta作为其元类(或直接继承自ABC)的类都将在其上具有register()方法。通过向ABC注册一个类,您正在发出信号,表明它继承自该ABC,即使它从技术上讲并非如此。这就是诚信协议发挥作用的地方。
from abc import ABC, abstractmethod

class MyABC(ABC):
    @abstractmethod
    def foo(self):
        """should return string 'foo'"""
        pass


class MyConcreteClass(object):
    def foo(self):
        return 'foo'

assert not isinstance(MyConcreteClass(), MyABC)
assert not issubclass(MyConcreteClass, MyABC)

虽然此时的MyConcreteClassMyABC无关,但它根据注释中规定的要求实现了MyABC的API。如果我们将MyConcreteClass注册到MyABC中,它将通过isinstanceissubclass检查。

MyABC.register(MyConcreteClass)

assert isinstance(MyConcreteClass(), MyABC)
assert issubclass(MyConcreteClass, MyABC)

再次强调,“善意协议”在此起作用。您不必遵循MyABC中规定的API。通过向ABC注册具体类,我们告诉任何外部检查器,我们程序员正在遵守应该遵守的API。

1请注意,@abstractproperty不再被推荐使用。相反,您应该使用:

@property
@abstractmethod
def foo(self):
    pass

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接