因为我习惯了Python中duck的老方法,所以我无法理解ABC(抽象基类)的需求。这本书很好地说明了如何使用它们。

我试图阅读PEP的基本原理,但我无法理解。如果我正在寻找可变序列容器,我会检查__setitem__,或者更有可能尝试使用它(EAFP)。我还没有遇到过数字模块的实际应用,它确实使用了abc,但这是我最接近的理解。

谁能给我解释一下原因吗?


当前回答

abc的一个方便的特性是,如果您没有实现所有必要的方法(和属性),那么在实例化时就会得到一个错误,而不是一个AttributeError,可能在很久以后,当您实际尝试使用缺少的方法时。

from abc import ABCMeta, abstractmethod

# python2
class Base(object):
    __metaclass__ = ABCMeta

    @abstractmethod
    def foo(self):
        pass

    @abstractmethod
    def bar(self):
        pass

# python3
class Base(object, metaclass=ABCMeta):
    @abstractmethod
    def foo(self):
        pass

    @abstractmethod
    def bar(self):
        pass

class Concrete(Base):
    def foo(self):
        pass

    # We forget to declare `bar`


c = Concrete()
# TypeError: "Can't instantiate abstract class Concrete with abstract methods bar"

例子来自https://dbader.org/blog/abstract-base-classes-in-python

编辑:包括python3语法,谢谢@PandasRocks

其他回答

它将使确定一个对象是否支持给定的协议变得更加容易,而不必检查协议中是否存在所有方法,也不必因为不支持而在“敌方”领土深处触发异常。

抽象方法确保你在父类中调用的任何方法都必须出现在子类中。下面是常用的调用和使用抽象的方法。 该程序用python3编写

正常呼叫方式

class Parent:
    def methodone(self):
        raise NotImplemented()

    def methodtwo(self):
        raise NotImplementedError()

class Son(Parent):
   def methodone(self):
       return 'methodone() is called'

c = Son()
c.methodone()

methodone()被调用

c.methodtwo()

NotImplementedError

抽象方法

from abc import ABCMeta, abstractmethod

class Parent(metaclass=ABCMeta):
    @abstractmethod
    def methodone(self):
        raise NotImplementedError()
    @abstractmethod
    def methodtwo(self):
        raise NotImplementedError()

class Son(Parent):
    def methodone(self):
        return 'methodone() is called'

c = Son()

不能用抽象方法methodtwo实例化抽象类Son。

因为methodtwo在子类中没有被调用,所以我们得到了错误。正确的实现如下所示

from abc import ABCMeta, abstractmethod

class Parent(metaclass=ABCMeta):
    @abstractmethod
    def methodone(self):
        raise NotImplementedError()
    @abstractmethod
    def methodtwo(self):
        raise NotImplementedError()

class Son(Parent):
    def methodone(self):
        return 'methodone() is called'
    def methodtwo(self):
        return 'methodtwo() is called'

c = Son()
c.methodone()

methodone()被调用

abc的一个方便的特性是,如果您没有实现所有必要的方法(和属性),那么在实例化时就会得到一个错误,而不是一个AttributeError,可能在很久以后,当您实际尝试使用缺少的方法时。

from abc import ABCMeta, abstractmethod

# python2
class Base(object):
    __metaclass__ = ABCMeta

    @abstractmethod
    def foo(self):
        pass

    @abstractmethod
    def bar(self):
        pass

# python3
class Base(object, metaclass=ABCMeta):
    @abstractmethod
    def foo(self):
        pass

    @abstractmethod
    def bar(self):
        pass

class Concrete(Base):
    def foo(self):
        pass

    # We forget to declare `bar`


c = Concrete()
# TypeError: "Can't instantiate abstract class Concrete with abstract methods bar"

例子来自https://dbader.org/blog/abstract-base-classes-in-python

编辑:包括python3语法,谢谢@PandasRocks

短的版本

abc在客户机和实现的类之间提供了更高级别的语义契约。

长版本

类和它的调用者之间有一个契约。该类承诺做某些事情并具有某些属性。

合同有不同的层次。

在非常低的级别上,契约可能包括方法的名称或其参数的数量。

在静态类型语言中,该契约实际上由编译器强制执行。在Python中,您可以使用EAFP或类型自省来确认未知对象是否符合此预期契约。

但是契约中也有更高层次的语义承诺。

例如,如果存在__str__()方法,则期望它返回对象的字符串表示形式。它可以删除对象的所有内容,提交事务并从打印机中吐出空白页……但是对于它应该做什么有一个共同的理解,在Python手册中有描述。

这是一种特殊情况,手册中描述了语义契约。print()方法应该做什么?它应该将对象写入打印机,还是将一行写入屏幕,还是其他什么?这要视情况而定——你需要阅读评论来理解这里的完整合同。一段客户端代码只是检查print()方法是否存在,从而确认了部分契约——可以进行方法调用,但不确定是否在调用的高级语义上达成了一致。

定义抽象基类(ABC)是在类实现者和调用者之间产生契约的一种方式。它不仅仅是一个方法名称列表,而是对这些方法应该做什么的共同理解。如果您继承了这个ABC,则承诺遵守注释中描述的所有规则,包括print()方法的语义。

Python的duck-typing在灵活性方面比静态类型有很多优势,但它并不能解决所有问题。abc提供了一种介于Python的自由形式和静态类型语言的约束和规则之间的中间解决方案。

ABC允许创建设计模式和框架。请看Brandon Rhodes的这段pycon谈话:

Python设计模式1

Python本身的协议(更不用说迭代器、装饰器和插槽(它们本身实现了FlyWeight模式)都是可能的,因为ABC的存在(尽管在CPython中实现为虚拟方法/类)。

Duck类型确实使python中的一些模式变得微不足道,Brandon提到过,但许多其他模式继续出现并在python中很有用,例如适配器。

简而言之,ABC使您能够编写可伸缩和可重用的代码。根据GoF:

针对接口编程,而不是实现(继承会破坏封装;接口编程促进松耦合/控制反转/“好莱坞原则:不要打电话给我们,我们会打电话给你”) 优先选择对象组合而不是类继承(委托工作) 封装变化的概念(开闭原则使类对扩展开放,但对修改关闭)

此外,随着Python的静态类型检查器(例如myypy)的出现,ABC可以用作类型,而不是Union[…]]用于函数接受作为参数或返回的每个对象。想象一下,每次您的代码库支持一个新对象时,都必须更新类型,而不是实现?这很快就变得不可维护(不能扩展)。