【问题标题】:Using abstract base class VS plain inheritance使用抽象基类 VS 普通继承
【发布时间】:2014-03-17 07:59:23
【问题描述】:

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

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

【问题讨论】:

  • @JayanthKoushik 我不认为这个问题是那个问题的重复,因为这是关于抽象基类与普通继承的问题,而另一个问题是关于抽象基类与鸭子的问题打字。

标签: python abstract-base-class


【解决方案1】:

除了您在问题中提到的内容外,就具体细节而言,最值得注意的答案是存在 @abstractmethod@abstractproperty1 装饰器,以及从 @987654324 继承@(或拥有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 无关,但它确实根据cmets 中规定的要求实现了MyABC 的API。现在,如果我们用MyABC注册MyConcreteClass,它将通过isinstanceissubclass检查。

MyABC.register(MyConcreteClass)

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

这也是“诚信协议”发挥作用的地方。您不必遵循 MyABC 中规定的 API。通过向 ABC 注册具体类,我们告诉任何外部检查员,我们,程序员,正在遵守我们应该遵守的 API。

1 请注意,@abstractproperty 不再是首选。相反,您应该使用:

@property
@abstractmethod
def foo(self):
    pass

【讨论】:

    猜你喜欢
    • 2010-10-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-09
    • 2014-08-26
    • 2012-05-20
    • 1970-01-01
    相关资源
    最近更新 更多