为什么在 Python 中使用抽象基类?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3570796/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why use Abstract Base Classes in Python?
提问by Muhammad Alkarouri
Because I am used to the old ways of duck typing in Python, I fail to understand the need for ABC (abstract base classes). The helpis good on how to use them.
因为我习惯了 Python 中鸭子类型的旧方法,所以我无法理解 ABC(抽象基类)的必要性。关于如何使用它们的帮助很好。
I tried to read the rationale in the PEP, but it went over my head. If I was looking for a mutable sequence container, I would check for __setitem__, or more likely try to use it (EAFP). I haven't come across a real life use for the numbersmodule, which does use ABCs, but that is the closest I have to understanding.
我试图阅读PEP 中的基本原理,但它让我无法理解。如果我正在寻找一个可变序列容器,我会检查__setitem__,或者更有可能尝试使用它(EAFP)。我还没有遇到过使用 ABC的numbers模块在现实生活中的用途,但这是我最了解的。
Can anyone explain the rationale to me, please?
任何人都可以向我解释理由吗?
采纳答案by Oddthinking
Short version
精简版
ABCs offer a higher level of semantic contract between clients and the implemented classes.
ABC 在客户端和实现的类之间提供了更高级别的语义契约。
Long version
长版
There is a contract between a class and its callers. The class promises to do certain things and have certain properties.
一个类和它的调用者之间有一个契约。该类承诺做某些事情并具有某些属性。
There are different levels to the contract.
合同有不同的层次。
At a very low level, the contract might include the name of a method or its number of parameters.
在非常低的级别上,合约可能包含方法的名称或其参数数量。
In a staticly-typed language, that contract would actually be enforced by the compiler. In Python, you can use EAFPor type introspection to confirm that the unknown object meets this expected contract.
在静态类型语言中,该契约实际上由编译器强制执行。在 Python 中,您可以使用EAFP或类型自省来确认未知对象是否满足此预期契约。
But there are also higher-level, semantic promises in the contract.
但是合约中也有更高级别的语义承诺。
For example, if there is a __str__()method, it is expected to return a string representation of the object. It coulddelete all contents of the object, commit the transaction and spit a blank page out of the printer... but there is a common understanding of what it should do, described in the Python manual.
例如,如果有一个__str__()方法,它应该返回对象的字符串表示。它可以删除对象的所有内容,提交事务并从打印机中吐出一张空白页......但是对于它应该做什么有一个共同的理解,在 Python 手册中有描述。
That's a special case, where the semantic contract is described in the manual. What should the print()method do? Should it write the object to a printer or a line to the screen, or something else? It depends - you need to read the comments to understand the full contract here. A piece of client code that simply checks that the print()method exists has confirmed part of the contract - that a method call can be made, but not that there is agreement on the higher level semantics of the call.
这是一种特殊情况,手册中描述了语义契约。该print()方法应该做什么?它应该将对象写入打印机还是将一条线写入屏幕,或者其他什么?这取决于 - 您需要阅读评论以了解此处的完整合同。一段简单地检查print()方法是否存在的客户端代码已经确认了合同的一部分 - 可以进行方法调用,但没有就调用的更高级别语义达成一致。
Defining an Abstract Base Class (ABC) is a way of producing a contract between the class implementers and the callers. It isn't just a list of method names, but a shared understanding of what those methods should do. If you inherit from this ABC, you are promising to follow all the rules described in the comments, including the semantics of the print()method.
定义抽象基类 (ABC) 是在类实现者和调用者之间产生契约的一种方式。它不仅仅是方法名称的列表,而是对这些方法应该做什么的共同理解。如果您从这个 ABC 继承,您承诺遵守注释中描述的所有规则,包括print()方法的语义。
Python's duck-typing has many advantages in flexibility over static-typing, but it doesn't solve all the problems. ABCs offer an intermediate solution between the free-form of Python and the bondage-and-discipline of a staticly-typed language.
Python 的duck-typing 在灵活性上比静态类型有很多优势,但它并不能解决所有问题。ABC 提供了 Python 的自由形式和静态类型语言的束缚和纪律之间的中间解决方案。
回答by Ignacio Vazquez-Abrams
It will make determining whether an object supports a given protocol without having to check for presence of all the methods in the protocol or without triggering an exception deep in "enemy" territory due to non-support much easier.
它将确定一个对象是否支持给定的协议,而不必检查协议中的所有方法是否存在,或者不会由于不支持而在“敌人”领域深处触发异常。
回答by Benjamin Hodgson
@Oddthinking's answer is not wrong, but I think it misses the real, practicalreason Python has ABCs in a world of duck-typing.
@Oddthinking 的回答并没有错,但我认为它错过了Python 在鸭子打字世界中拥有 ABC的真实、实用的原因。
Abstract methods are neat, but in my opinion they don't really fill any use-cases not already covered by duck typing. Abstract base classes' real power lies in the way they allow you to customise the behaviour of isinstanceand issubclass. (__subclasshook__is basically a friendlier API on top of Python's __instancecheck__and __subclasscheck__hooks.) Adapting built-in constructs to work on custom types is very much part of Python's philosophy.
抽象方法很简洁,但在我看来,它们并没有真正填补鸭子类型尚未涵盖的任何用例。抽象基类真正的力量在于他们的方式让你自定义的行为isinstance和issubclass。(__subclasshook__基本上是 Python__instancecheck__和__subclasscheck__钩子之上的一个更友好的 API 。)调整内置构造以处理自定义类型是 Python 哲学的重要组成部分。
Python's source code is exemplary. Hereis how collections.Containeris defined in the standard library (at time of writing):
Python 的源代码堪称典范。以下是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
This definition of __subclasshook__says that any class with a __contains__attribute is considered to be a subclass of Container, even if it doesn't subclass it directly. So I can write this:
这个定义__subclasshook__表示任何具有__contains__属性的类都被认为是 Container 的子类,即使它没有直接子类化它。所以我可以这样写:
class ContainAllTheThings(object):
def __contains__(self, item):
return True
>>> issubclass(ContainAllTheThings, collections.Container)
True
>>> isinstance(ContainAllTheThings(), collections.Container)
True
In other words, if you implement the right interface, you're a subclass!ABCs provide a formal way to define interfaces in Python, while staying true to the spirit of duck-typing. Besides, this works in a way that honours the Open-Closed Principle.
换句话说,如果你实现了正确的接口,你就是一个子类!ABCs 提供了一种在 Python 中定义接口的正式方法,同时忠于鸭子类型的精神。此外,这以一种尊重开闭原则的方式工作。
Python's object model looks superficially similar to that of a more "traditional" OO system (by which I mean Java*) - we got yer classes, yer objects, yer methods - but when you scratch the surface you'll find something far richer and more flexible. Likewise, Python's notion of abstract base classes may be recognisable to a Java developer, but in practice they are intended for a very different purpose.
Python 的对象模型在表面上看起来类似于更“传统”的 OO 系统(我指的是 Java*)——我们有你的类、你的对象、你的方法——但是当你触及表面时,你会发现一些更丰富和更灵活。同样,Java 开发人员可能会识别 Python 的抽象基类概念,但实际上它们用于非常不同的目的。
I sometimes find myself writing polymorphic functions that can act on a single item or a collection of items, and I find isinstance(x, collections.Iterable)to be much more readable than hasattr(x, '__iter__')or an equivalent try...exceptblock. (If you didn't know Python, which of those three would make the intention of the code clearest?)
有时我发现自己编写了可以作用于单个项目或项目集合的多态函数,并且我发现isinstance(x, collections.Iterable)它比hasattr(x, '__iter__')或等效try...except块更具可读性。(如果您不了解 Python,那么这三个中的哪一个会使代码的意图最清楚?)
That said, I find that I rarely need to write my own ABC and I typically discover the need for one through refactoring. If I see a polymorphic function making a lot of attribute checks, or lots of functions making the same attribute checks, that smell suggests the existence of an ABC waiting to be extracted.
也就是说,我发现我很少需要编写自己的 ABC,而且我通常通过重构发现需要一个。如果我看到一个多态函数进行了大量的属性检查,或者许多函数进行了相同的属性检查,那么这种气味表明存在一个等待提取的 ABC。
*without getting into the debate over whether Java is a "traditional" OO system...
*没有进入关于 Java 是否是“传统的”面向对象系统的争论......
Addendum: Even though an abstract base class can override the behaviour of isinstanceand issubclass, it still doesn't enter the MROof the virtual subclass. This is a potential pitfall for clients: not every object for which isinstance(x, MyABC) == Truehas the methods defined on MyABC.
附录:即使抽象基类可以覆盖isinstanceand的行为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
Unfortunately this one of those "just don't do that" traps (of which Python has relatively few!): avoid defining ABCs with both a __subclasshook__and non-abstract methods. Moreover, you should make your definition of __subclasshook__consistent with the set of abstract methods your ABC defines.
不幸的是,这是一个“只是不要那样做”的陷阱(其中 Python 相对较少!):避免使用 a__subclasshook__和非抽象方法定义 ABC 。此外,您应该使您的定义__subclasshook__与您的 ABC 定义的抽象方法集一致。
回答by cerberos
A handy feature of ABCs is that if you don't implement all necessary methods (and properties) you get an error upon instantiation, rather than an AttributeError, potentially much later, when you actually try to use the missing method.
ABCs 的一个方便的特性是,如果您没有实现所有必要的方法(和属性),您将在实例化时收到错误,而不是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"
Example from https://dbader.org/blog/abstract-base-classes-in-python
来自https://dbader.org/blog/abstract-base-classes-in-python 的示例
Edit: to include python3 syntax, thanks @PandasRocks
编辑:包含 python3 语法,感谢@PandasRocks
回答by sim
Abstract method make sure that what ever method you are calling in the parent class has to be appear in child class. Below are noraml way of calling and using abstract. The program written in python3
抽象方法确保您在父类中调用的任何方法都必须出现在子类中。下面是调用和使用抽象的规范方式。python3编写的程序
Normal way of calling
正常调用方式
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() is called'
'methodone() 被调用'
c.methodtwo()
NotImplementedError
未实现错误
With Abstract method
使用抽象方法
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: Can't instantiate abstract class Son with abstract methods methodtwo.
类型错误:无法使用抽象方法 methodtwo 实例化抽象类 Son。
Since methodtwo is not called in child class we got error. The proper implementation is below
由于没有在子类中调用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() is called'
'methodone() 被调用'

