为什么在Python中使用抽象基类?

为什么在Python中使用抽象基类?,python,abstract-class,abc,Python,Abstract Class,Abc,因为我习惯了Python中duck类型的老方法,所以我无法理解ABC(抽象基类)的必要性。在如何使用它们方面,这本书很好 我试图读懂报告中的理由,但它超出了我的理解范围。如果我正在寻找一个可变序列容器,我会检查\uuuu setitem\uuuu,或者更可能尝试使用它()。我还没有接触到该模块的实际使用,它确实使用ABCs,但这是我最接近理解的 谁能给我解释一下原理吗?这将使确定一个对象是否支持给定的协议变得更容易,而不必检查协议中是否存在所有方法,也不必因为不支持而触发“敌人”领土深处的异常。

因为我习惯了Python中duck类型的老方法,所以我无法理解ABC(抽象基类)的必要性。在如何使用它们方面,这本书很好

我试图读懂报告中的理由,但它超出了我的理解范围。如果我正在寻找一个可变序列容器,我会检查
\uuuu setitem\uuuu
,或者更可能尝试使用它()。我还没有接触到该模块的实际使用,它确实使用ABCs,但这是我最接近理解的


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

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

简短版本 ABC在客户端和实现的类之间提供了更高级别的语义契约

长版本 类和它的调用方之间有一个约定。这个类承诺做某些事情并具有某些属性

合同有不同的层次

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

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

但合同中也有更高层次的语义承诺

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

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

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


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

@Oddthinking的答案没有错,但我认为它忽略了Python在duck类型世界中使用ABC的真正实际原因

抽象方法很简洁,但在我看来,它们并不能真正填充duck类型尚未涵盖的任何用例。抽象基类的真正力量在于。(
\uuuuu subclass钩子\uuuuuu
基本上是Python钩子之上的一个更友好的API。)调整内置结构以处理自定义类型是Python理念的一部分


Python的源代码是典型的。是在标准库中定义
集合.容器的方式(撰写本文时):

\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。所以我可以这样写:

class ContainAllTheThings(object):
    def __contains__(self, item):
        return True

>>> issubclass(ContainAllTheThings, collections.Container)
True
>>> isinstance(ContainAllTheThings(), collections.Container)
True

换句话说,如果你实现了正确的接口,你就是一个子类!ABC提供了一种在Python中定义接口的正式方法,同时忠实于duck类型的精神。除此之外,这项工作在某种程度上尊重了公众

Python的对象模型表面上看起来与更“传统”的OO系统(我指的是Java*)相似——我们有了你们的类、对象和方法——但当你们触及表面时,你们会发现一些更丰富、更灵活的东西。类似地,Java开发人员可能可以识别Python抽象基类的概念,但实际上它们的目的完全不同

有时我发现自己编写的多态函数可以作用于单个项或项的集合,并且我发现
isinstance(x,collections.Iterable)
hasattr(x,'.\uu iter'.')
或等效的
try…除了
block之外更可读。(如果您不懂Python,那么这三个选项中哪一个会使代码的意图最清晰?)

也就是说,我发现我很少需要编写自己的ABC,而且我通常通过重构发现需要一个。如果我看到一个多态函数进行许多属性检查,或者许多函数进行相同的属性检查,那么这种气味表明存在一个等待提取的ABC

*没有讨论Java是否是一个“传统”的OO系统


附录:即使抽象基类可以重写
isinstance
issubclass
的行为,它仍然不会进入虚拟子类的状态。这对于客户端来说是一个潜在的陷阱:不是每个对象的
isinstance(x,MyABC)==True
都有
MyABC
上定义的方法

class MyABC(metaclass=abc.ABCMeta):
    def abc_method(self):
        pass
    @classmethod
    def __subclasshook__(cls, C):
        return True

class C(object):
    pass

# typical client code
c = C()
if isinstance(c, MyABC):  # will be true
    c.abc_method()  # raises AttributeError
不幸的是,这是一个“不要那样做”的陷阱(Python的陷阱相对较少!):避免使用
\uuuu子类hook\uuuu
和非抽象方法来定义ABC。此外,您应该使
\uuuuu子类hook\uuuu
的定义与ABC定义的抽象方法集保持一致
class MyABC(metaclass=abc.ABCMeta):
    def abc_method(self):
        pass
    @classmethod
    def __subclasshook__(cls, C):
        return True

class C(object):
    pass

# typical client code
c = C()
if isinstance(c, MyABC):  # will be true
    c.abc_method()  # raises 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"
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()
c.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'

c = Son()
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()