使用抽象基类VS普通继承



我试图理解使用抽象基类的好处。考虑这两段代码:

抽象基类:

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使用纯继承,您的代码将不会失败,直到您实际调用有问题的方法/属性。

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

在具体细节方面,除了你在问题中提到的,最值得注意的答案是@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

相关内容

  • 没有找到相关文章

最新更新