使用抽象基类VS普通继承

时间:2014-03-17 07:59:23

标签: python abstract-base-class

我正在尝试了解使用abstract base classes的好处。考虑这两段代码:

抽象基类:

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使用普通继承,则在实际调用相关方法/属性之前,代码不会失败。

除此之外,使用抽象基类的原因是什么?

1 个答案:

答案 0 :(得分:1)

除了您在问题中提到的以外,就具体细节而言,最值得注意的答案是@abstractmethod@abstractproperty 1 装饰符的存在从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中引入了抽象基类。我建议您通读“定额”部分,以了解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)

虽然MyConcreteClass在这一点上与MyABC无关,但它确实根据注释中提出的要求实现了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