因为我习惯于在Python中使用旧的鸭子打字方式,所以我无法理解ABC(抽象基类)的必要性。 help很好用。如何使用它们。
我试图阅读PEP中的理由,但它超越了我的脑海。如果我正在寻找一个可变序列容器,我会检查__setitem__
,或者更有可能尝试使用它(EAFP)。我没有遇到numbers模块的实际用途,它确实使用了ABC,但这是我最接近理解的模块。
有人可以向我解释理由吗?
答案 0 :(得分:196)
@ Oddthinking的答案并没有错,但我认为它错过了真正的,实用的原因,Python在鸭子打字的世界中拥有ABC。
抽象方法很简洁,但在我看来,它们并没有真正填充任何未被鸭子打字所涵盖的用例。抽象基类的实力在于the way they allow you to customise the behaviour of isinstance
and issubclass
。 (__subclasshook__
基本上是Python __instancecheck__
and __subclasscheck__
挂钩之上的友好API。)调整内置构造以处理自定义类型是Python理念的一部分。
Python的源代码是典型的。 Here是标准库(撰写本文时)中collections.Container
的定义方式:
class Container(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __contains__(self, x):
return False
@classmethod
def __subclasshook__(cls, C):
if cls is Container:
if any("__contains__" in B.__dict__ for B in C.__mro__):
return True
return NotImplemented
__subclasshook__
的这个定义说任何具有__contains__
属性的类都被认为是Container的子类,即使它没有直接对它进行子类化。所以我可以这样写:
class ContainAllTheThings(object):
def __contains__(self, item):
return True
>>> issubclass(ContainAllTheThings, collections.Container)
True
>>> isinstance(ContainAllTheThings(), collections.Container)
True
换句话说,如果你实现了正确的接口,你就是一个子类! ABCs提供了一种在Python中定义接口的正式方法,同时坚持鸭子类型的精神。此外,它的工作方式是尊重Open-Closed Principle。
Python的对象模型看起来表面上类似于更“传统”的OO系统(我的意思是Java *) - 我们得到了你的类,你的对象,你的方法 - 但是当你划伤表面时你会发现一些东西更丰富,更灵活。同样,Python的抽象基类概念对于Java开发人员来说可能是可识别的,但在实践中它们的目的非常不同。
我有时会发现自己编写的多态函数可以作用于单个项目或项目集合,我发现isinstance(x, collections.Iterable)
比hasattr(x, '__iter__')
或等效try...except
更具可读性。块。 (如果你不了解Python,那三个中的哪一个会使代码的意图最清楚?)
那就是说,我发现我很少需要编写自己的ABC,而且我通常会发现需要通过重构。如果我看到一个多态函数进行了大量的属性检查,或者许多函数进行了相同的属性检查,那气味就表明存在等待提取的ABC。
*没有讨论Java是否是一个“传统的”OO系统...
附录:即使抽象基类可以覆盖isinstance
和issubclass
的行为,它仍然不会进入虚拟的MRO子类。这对客户来说是一个潜在的陷阱:并非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相对较少!):避免使用__subclasshook__
和非抽象方法定义ABCs。此外,您应该使__subclasshook__
的定义与ABC定义的抽象方法集合一致。
答案 1 :(得分:138)
ABCs在客户端和已实现的类之间提供更高级别的语义契约。
课程与其来电者之间存在合同。该班承诺做某些事情并具有某些特性。
合同有不同的级别。
在非常低的级别,合同可能包括方法的名称或参数的数量。
在静态类型语言中,该合同实际上是由编译器强制执行的。在Python中,您可以使用EAFP或内省来确认未知对象是否符合此预期合同。
但合同中也有更高级别的语义承诺。
例如,如果存在__str__()
方法,则应返回该对象的字符串表示形式。它可以删除对象的所有内容,提交事务并从打印机中吐出一个空白页......但是对于它应该做什么有共同的理解,如Python手册中所述。 / p>
这是一个特殊情况,其中语义合同在手册中描述。 print()
方法应该做什么?它应该将对象写入打印机还是屏幕线或其他东西?这取决于 - 您需要阅读评论以了解完整的合同。一个简单地检查print()
方法存在的客户端代码已经确认了合同的一部分 - 可以进行方法调用,但不是就调用的更高级别语义达成一致。
定义抽象基类(ABC)是一种在类实现者和调用者之间生成契约的方法。它不仅仅是方法名称列表,而是对这些方法应该做什么的共同理解。如果您继承此ABC,则承诺遵循评论中描述的所有规则,包括print()
方法的语义。
Python的鸭子打字在灵活性方面比静态打字有很多优点,但它并没有解决所有问题。 ABCs提供了一种自由形式的Python与静态类型语言的束缚和规范之间的中间解决方案。
答案 2 :(得分:87)
ABCs的一个方便功能是,如果你没有实现所有必要的方法(和属性),你实例化时会出现错误,而不是AttributeError
,可能更晚,当你真正尝试使用缺少方法。
VBA-Web - Installer.xlsm
来自https://dbader.org/blog/abstract-base-classes-in-python
的示例编辑:包含python3语法,感谢@PandasRocks
答案 3 :(得分:15)
它将确定一个对象是否支持给定的协议,而不必检查协议中是否存在所有方法,或者由于不支持而更容易在“敌方”区域内触发异常。
答案 4 :(得分:5)
抽象方法确保您在父类中调用的任何方法都必须出现在子类中。以下是noraml调用和使用摘要的方式。 用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()
TypeError:无法使用抽象方法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()被称为'