<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 本章讨论的话题是接口，从鸭子类型代表特征动态协议，到使接口更明确，能验证是否符合规定的抽象基类（Abstract Base Class，ABC）
在 Python 中 上章所说的鸭子类型是接口的常规方式，新只是是抽象基类和类型检查。Python 语言诞生 15 年之后，Python 2.6 才引入抽象基类。
本章先说明 Python 社区以往对接口的不严谨理解：部分实现接口通常被认为是可接受的。我们通过几个示例强调鸭子类型的动态本性，从而澄清这一点
接着，通过 Alex Martelili 写的一篇短文，对抽象基类作介绍，还为 Python 编程下一个新趋势下定义。本章余下的内容专门讲解抽象基类。首先，本章说明抽象基类的常见用途：实现接口时作为超类使用。
然后，说明抽象基类如何检查具体子类是否符合接口定义，以及如何使用注册机制声明一个类实现了某个接口，而不进行子类化操作。最后，说明如何让抽象基类自动 ”识别“ 任何符合接口的类 -- 不进行子类化或注册
我们将实现一个新抽象基类，看看它的运作方式。但是，作者和 Alex Martelli 都不建议你自己编写抽象基类，因为容易过度设计
抽象基类与描述符和元类一样，是用于构建框架的工具。因此，只有少数 Python 开发者编写的抽象基类不会对用户施加不必要的限制，让他们做无用功
下面从 Python 风格探索接口
Python 文化中的接口和协议
在引入抽象基类之前，Python 已经非常成功了，即使现在也很少有代码使用抽象基类。在第一章就讨论了鸭子类型和协议，在上一章，我们将协议定义为非正式的接口，是让 Python 这种动态类型语言实现多态的方式。
接口在动态类型语言是怎么运作的呢？首先，基本的事实是，Python 语言没有 interface 关键字，而且除了抽象基类，每个类都有接口：类实现或继承的公开属性（方法或数据的属性），包括特殊方法，如 __getitem__ 或 __add__ 
按照定义，受保护的属性和私有属性不在接口中：即便有“受保护”属性也只是采用命名约定实现的（单个前导下划线）私有属性也可以轻松的访问（第 9 章），原因也是如此，不要违背这些约定
另一方面，不要觉得把公开数据属性放入对象接口中不妥，因为如果需要，总能实现读值方法和设值方法，把数据属性变成特性，使用 obj.attr 语法的客户代码不会受到影响。Vector2d 类就是这么做的
下面的例子 x，y 是公开属性
Step1:  在第 9 章中，我们将其变成了只读属性，这是重大的重构，但是 Vector2d 的接口基本没变，用户仍然能读取 my_vector.x 和 my_vector.y。下面是使用特性实现 x，y（第 9 章的代码）
Step2:  关于接口，这里有个实用的补充定义：对象公开方法的子集，让对象在系统中扮演特定的角色。Python 文档中的 “文件类对象” 或 “可迭代对象” 就是这个意思，这种说法指的不是特定的类。接口是实现特定角色的方法集合，这样理解正是 Smalltalk 程序员说的协议，其他动态预言社区都借鉴了这个术语，协议与继承没有关系。一个类可能会实现多个接口，从而让实例扮演多个角色
协议是接口，但不是正式的（只由文档和约定定义），因此协议不能像正式接口那样施加限制（本章后面会说明抽象基类对接口一致性的强制）。一个类可能只实现部分接口，这是允许的。有时，某些 API 只要求 “文件类对象” 返回字节序列 .read() 方法。在特定的上下文中可能需要其他文件操作方法，也可能不需要
作者写书时候，Python 3 中的 memoryview 的文档说，它能处理“支持缓冲协议的对象“，不过缓冲协议的文档是 C API 的。 bytearray 的构造方法接受”一个符合缓冲接口的对象”。如今，文档正在改变用词，使用“字节序列类对象”这样更加友好的表述。我指出这一点是为了强调，对 Python 程序员来说，'X' 类对象 和 'X' 协议和 'X' 接口都是一个意思
序列协议是 Python 是最基础的协议之一。即使对象只实现了那个协议的最基本的一部分，解释器也能负责任的处理，如下一节所示。
Python 喜欢序列
Python 数据模型的哲学是尽量支持基本协议。对序列来说，即便是最简单的实现，Python 也会力求做的最好
抽象基类 Sequence 的正式接口如下：__getitem__, __contains__, __iter__, __reversed__, index, count
下面的例子 Foo 类没有继承 abc.Sequence，而且只实现了序列协议的一个方法
Step3:  虽然没有 __iter__ 方法，但是 Foo 实例是可迭代对象，因为发现有 __getitem__ 方法时，Python 会调用它，传入从 0 开始的整数索引，尝试迭代对象（这是一种后备机制）。尽管没有实现 __contains__ 方法，但是 Python 足够只能，能迭代 Foo实例，因此也能使用 in 运算符：Python 会做全面检查，看看有没有指定的元素
综上，鉴于序列协议的重要性，如果没有 __iter__ 和 __contains__ 方法，Python 会调用 __getitem__ 方法，设法让迭代和 in 运算符可用
第一章定义的 FrenchDeck 类也没有继承 abc.Sequence，但是实现了序列协议的两个方法
Step4:  下面分析一个示例，着重强调协议的动态本性
使用猴子补丁在运行时实现协议
在上面 FrenchDeck 类有一个重大的缺陷：无法洗牌。几年前，第一次编写 FrenchDeck 示例时，我发现了 shuffle 方法。后来。我对 Python 风格有了深刻理解，我发现如果 FrenchDeck 实例的行为像序列，那么就不需要 shuffle 方法，因为已经有 random.shuffle 函数可用，文档中说它的作用就是就地打乱序列
Step5:  然而，要打乱 FrenckDeck 函实例，会出现异常，如下所示：
Step6:  错误消息很明确，FrenchDeck 对象不支持为元素赋值。这个问题的原因是，FrenchDeck 只实现了不可变的序列协议，可变序列还需要提供 __setitem__ 方法。
Python 是动态语言，因此我们可以在运行时修正这个问题，甚至还可以在交互式控制台中，修正方法如下所示：
Step7:  __setitem__ 在语言参考中使用的参数是 self, key, value，我们这里用的是 deck, position 和 card，这是为了提醒我们，每个 Python 方法说到底就是一个普通的函数，把第一个参数命名为 self 是一种约定。在控制台会话使用那几个参数没问题，但是在 Python 源码文件中最好按照文档那样使用 self, key, value
这里的关键是，set_card 函数要知道 deck 对象有一个名为 _cards 的属性，而且 _cards 必须是可变序列，然后吧 setcard 方法赋给特殊方法 __setitem__，从而把它依附到 FrenchDeck 类上。这种技术叫猴子补丁：在运行时修改类或模块，而不改动源码，猴子补丁很强大，但是打补丁的代码与要补丁的程序耦合十分紧密，而且往往要处理隐藏和没有文档的部分
除了举例的猴子补丁之外，上面例子还强调了协议是动态的：random.shuffle() 函数不关心参数类型，只要那个对象实现了部分可变序列协议即可。即便对象一开始没有所需的方法也没关系，后来再提供也行
目前，本章讨论的主题是 “鸭子类型”：对象的类型无关紧要，只要实现了特定的协议即可。
前面给出的抽象基类 Sequence 是为了展示协议与抽象基类的文档中所说的接口之间的关系，但是目前为止还没有真正的实现抽象基类
在下面的几节中，我们将直接使用抽象基类，而不是将其视为文档
Alex Martelli 的水禽
Alex Martelli 讲故事：
wiki 说我协助传播了“鸭子类型”这种言简意赅的说法（即斛律对象的真正类型，转而关注对象有没有实现所需的方法，签名和语义）
对 Python 来说，这基本上指避免使用 instance 检查对象类型(更别提 type(foo) is bar 这种更糟糕的检查方式了，这样做没有任何好处，甚至禁止最简单的继承方式）。
总的来说，鸭子类型很有用，但是有的时候继承的方式更好。
在生物学中，平行进化会导致不相关的种类产生相似的属性，形态和举止方面都是如此，但是生态位的相似性是偶然的，不同的种类仍然属于不同的生态位。编程语言也有这种“偶然的相似性”，比如下面经典的面向对象编程示例：
Step8:  显然，因为 x，y 两个对象刚好都有一个名为 draw 的方法，而且调用时候不用传入参数，即 x.draw() 和 y.draw()，远远不能确保二者可以相互调用，或者具有相同的抽象。也就是说，从这样的调用中不能推导出语义的相似性。相反，我们需要一位渊博的程序员主动把这种等价维持在一定层次上。
例如，草雁（以前认为与其他鹅类比较类似）和麻鸭（以前认为与其他鸭类比较类似）现在被分到 Tadornidae 亚科（表明二者相似性比鸭科其他动物高，因为他们的共同祖先比较接近）。此外，DNA 分析表明，白翅木鸭与美洲家鸭（属于麻鸭）不是很像，至少没有形态和举止看起来那么像，因此把木鸭单独分成了一组，完全不再 Tadornidae 亚科中。
知道这些有什么用呢？视情况而定，比如，逮到一直水禽之后，决定如何烹制才最美味时，显著的特征（不是全部，例如一身羽毛并不重要）主要是口感和风味（过时的表征学），这比支序学重要的多。但是其他方面，如对不同病原体的抗性（圈养水禽还是放养），DNA 接近性的作用就大多了。
因此，参照水禽的分类学演化，我建议在鸭子类型基础上增加白鹅类型（goose typing)。
白鹅类型是指，只要 cls 是抽象基类，即 cls 元类是 abcABCMeta 就可以使用 isinstance(obj, cls)。
colections.abc 中有很多有用的抽象基类（Python 标准库的 numbers 模块还有一些）。
与具体类相比，抽象基类有很多理论上的优点，Python 抽象基类还有一个重要的实用优势：可以使用 register 类方法在终端用户的代码把某个类 “声明”  为一个抽象基类的 “虚拟” 子类（为此，被注册的类必须满足抽象基类对方法名称和签名的要求，最重要的是要满足底层语义契约;但是，开发那个类时不用了解抽象基类，更不用继承抽象基类）。这大大打破了严格的强耦合，与面向对象编程人员掌握的知识有很大出入，因此使用继承时要小心
有时，为了让抽象基类识别子类，甚至不用注册。
其实，抽象基类的本质就是几个特殊方法。例如：
Step9:  可以看出，无需注册，abc.Sized 也能把 Struggle 识别为自己的子类，只要实现了特殊方法 __len__ 即可（要使用正确的语法和语义实现，前者要求没有参数，后者要求返回一个非负整数，指明对象的长度;如果不使用规定的语法和语义实现 __len__ 方法，会导致非常严重的问题）
最后要说的是：如果实现的类具体实现了 numbers, collections.abc 或者其他框架中的抽象基类的概念，要么继承相应的抽象基类（必要时），要么类注册到相应的抽象基类中。开始开发程序时，不要使用提供注册功能的库或框架，要自己动手注册，如果必须检查参数的类型（这是最常见的），例如检查是不是 “序列”，那么就这么做：
isinstance(the_arg, collections.abc.Sequence)
此外，不要在生产代码中定义抽象基类（或元类）。。。如果你很想这样做，我打赌你可能要找茬（= =），刚拿到新工具的人都有大干一场的冲动。如果你能避开这些深奥的概念，你（以及未来的代码维护者）的生活将更加愉快，因为代码会变得简洁明了，讲完啦～
除了提出 ”白鹅类型“ 之外，Alex 还指出，继承抽象基类很简单，只需要实现所需的方法，这样也能明确表明开发者意图，这一意图还能通过注册虚拟子类实现。
此外，使用 isinstance 和 issubclass 测试抽象基类更为人接受，过去，这两个函数用来测试鸭子类型，但用于抽象基类会更加灵活。毕竟，如果某个组件没有继承抽象基类，事后还可以注册，让显式类型检查通过
然而即使是抽象基类，也不能滥用 isinstance 检查，用多了可能导致代码很糟糕。在一连串 if/elif/elif 中使用 isinstance 做检查，然后根据对象类型做不同操作，是十分糟糕的做法，此时应该使用堕胎，即使用一定的方式定义类，让解释器把调用分派给正确的方法，而不是采用 if/elif/elif 块硬编码分派逻辑
具体使用时，上述建议有一个常见的例外：有些 Python API 接受一个字符串或字符串序列，如果只有一个字符串，可以把它放到列表中，从而简化处理。因为字符串是序列类型，所以为了把他和其它不可变序列区分，最简单的方式是使用 isinstance(x, str) 检查
可惜，Python 3.4 没有能把字符串和元组或者其他不可变序列区分开的抽象基类，因此必须测试 str。在 Python 2 中，basestr 类型可以协助这样的测试。basestr 不是抽象基类，但是他是 str 和 unicode 的超类。然而，Python 3 却把 basestr 去掉了，奇怪的是，Python 3 中有个 collections.abc.ByteString 类型，但是只能检测 byte 和 bytearray 类型
另一方面，如果必须强制执行 API 契约，通常可以使用 isinstance 检查抽象基类。“老兄，如果你想调用我，必须实现这个”，正如本书审校 Lennart Regebro 所说。这对采用插入式架构的系统来说特别有用。在框架之外，鸭子类型通常比类型检查更简单灵活
例如，本书有几个示例要使用序列，把他当成列表，我没有检查参数的类型是不是 list，而是直接接受参数，立即使用它构建一个列表。这样，我就可以接受任何可迭代对象，如果参数不是可迭代对象，调用立即失败，并提供清晰的错误信息，本章后面有一个这样的例子。当然，如果序列太长或者需要就地修改序列而导致无法复制参数，就不能采用这种方式，此时，使用 isinstance(x, abc.MutableSequence) 更好，如果可以接受任何可迭代对象，也可以调用 iter(x) 函数获得一个迭代器，第 14 章详细讲
模仿 collections.namedtuple 处理 field_names 参数的方式也是一例，field_names 的值可以是单个字符串，以空格或者逗号分隔符，也可以是一个标识符序列，此时可能想使用 isinstance，但是我会使用鸭子类型，如下所示：
使用鸭子类型处理单个字符串或者由字符串组成的可迭代对象：
Step10:  最后再 Alex Martelli 强调一下建议尽量不要自己实现抽象基类，容易造成灾难性后果，下面通过实例讲解白鹅类型：
定义抽象基类的子类
我们按照 Martelli 的建议，先利用现有抽象基类（collections.MutableSequence)，然后再自己定义。在下面例子，我们将 FrenchDeck2 声明为collections.MutableSequence 的子类
Step11:  Python 不会在模块导入时候检查抽象方法的实现，而是在实例化 FrenchDeck2 类时才会真正的检查。因此，如果没有正确实现某个抽象方法，Python 会抛出 TypeError 异常，并把错误消息设为 “Can't instantiate abstract class FrenchDeck2 with abstract methods __delitem__, insert"。正是这个原因，即使 FrenchDeck2 类不需要 __delitem__ 和 insert 提供的行为，但是因为继承 MutableSequence 抽象基类，必须实现他们。
FrenchDeck2 从 Sequence（因为 MultableSequence 继承了 Sequence） 继承了几个拿来即用的具体方法 __contains__, __iter__, __reversed__, index 和 count。FrenchDeck2 从 MutableSequence 继承了 append，extend，pop，remove 和 __iadd__
在 collections.abc 中，每个抽象基类的具体方法都是作为类的公开接口实现的，因此不用知道实例的内部接口
要想实现子类，可以覆盖抽象基类中继承的方法，以更高效的方式实现，例如，__contains__ 方法会全面扫描序列，如果面门定义的序列按顺序保存元素，就可以重新实现 __contain__ 方法，使用 bisect 函数做二分查找，提高速度
标准库中的抽象基类
Python 2.6 开始，标准库提供了抽象基类，大多数抽象基类在 collections.abc 模块中定义，不过其他地方也有，例如 numbers 和 io 包中包含一些，但是 collections.abc 中的抽象基类最常用，我们看看这个模块有那些抽象基类
标准库有两个 abc 模块，我们讨论的是 collections.abc 为了减少加载时间， Python 3.4 在 collections 包之外实现了这个模块，因此要与 cloections 分开导入。另一个 abc 模块就是 abc，这里定义的是 abc.ABC 类，每个抽象基类都依赖这个类，但是不用导入他，除非重新定义新的抽象基类
下面介绍几个抽象基类：
Iterable, Container, Sized
每个集合都应该继承这 3 个抽象基类，或者至少要实现兼容协议。Iterable 通过 __iter__ 方法支持迭代，Ccontainer 通过 __contains__ 方法支持 in 运算符，Sized 通过 __len__ 方法支持 len 函数
Sequence, Mapping 和 Set
这三个是主要的不可变集合类型，而且各自都有可变的子类。
MappingView
在 Python 3 中，映射方法 .items(), .keys(), .values() 返回的对象分别时 ItemsView，KeysView 和 ValuesView 的实例。前两个类还从 Set 类继承了丰富的接口，包含第 3 章所述的所有运算符
Callable 和 Hashable 
这两个抽象基类和集合没有太大关系，只不过因为 collections.abc 是标准库中定义抽象基类的第一个模块，而他们又太重要了，所以才放到 collections.abc 模块中。我从未见过 Callable 或 Hashable 的子类。这两个抽象基类的主要作用是为内置函数 isinstance 提供支持提供支持，以一种安全的方式判断对象能不能调用或三裂
若检查是否能调用，可以使用内置的 callable() 函数，但是没有类似的 hashable() 函数，因此测试对象是否可散列，最好使用 isinstance(my_obj, Hashable)
Iterator
注意它是 Iterable 的子类，我们在第 14 章继续讨论
继 collections.abc 之后，标准库中最有用的抽象基类包是 numbers，我们来介绍一下
抽象基类的数字塔
numbers 包定义的是 “数字塔”（即各个抽象基类的层次结构是线性的），其中 Number 是位于最顶端的超类，随后是 Complex，依次往下，最底端是 Integral 类
Number
Complex
Real
Rational
Integral
因此，要检查一个数是不是整数，可以使用 isinstance(x, numbers.Integral)，这样代码就能接受 int, bool（inti 的子类），或者外部库使用 numbers 抽象基类注册的其他类型。为了满足检查需要，你或者你的 API 用户始终可以把兼容的类型注册为 numbers.Integral 的虚拟子类
与之类似，如果一个值可能是浮点数类型，可以使用 isinstance(x, numbers.Real) 检查。这样代码就能接受 bool，int，float，fractions.Fraction 或者外部库（如 Numpy，它做了相应注册）提供非复数的类型
decimal.Decimal 没有注册为 numbers.Real 的虚拟子类，这有写奇怪。没注册的原因时，如果你的程序需要 Decimal 的精度，要防止与其他低精度的数字类型混淆，尤其是浮点数
了解一些现有的抽象基类之后，我们将从零开始实现一个抽象基类，然后开始使用，以此实现白鹅类型。这么做的目的不是鼓励每个人都立即定义抽象基类，而是教你怎么阅读标准库和其他包中的抽象基类源码。
定义并使用一个抽象基类
假如我们要在网站随机显示广告，但是在整个广告清单轮转一遍，不重复显示广告。我们正在构建一个广告管理框架，名叫 ADAM，它的职责之一是，支持用户随机挑选无重复类，我们将为此定义一个抽象基类
收到栈和队列启发，我们将使用现实中物体命名这个抽象基类：宾果机和彩票机是随机从有限集合挑选物品的机器，选出的物品没有重复，直到选完为止
我们将这个抽象基类命名为 Tombola，这是宾果机和打乱数字的滚动容器的意大利名字
Tombla 抽象基类有四个方法，其中两个是抽象方法。
.load(...) 把元素放入容器
.pick() 从容器中随机拿出一个元素，返回选中的元素
下面两个是具体方法：
.loaded() 如果容器中至少有一个元素，返回 True
.inspect() 返回一个有序元组，由容器现有元素构成，不会修改容器内容
抽象基类的定义如下所示：
Step12:  其实，抽象方法也可有实现代码，即使实现了，子类也必须覆盖抽象方法，但是在子类中可以用 super() 函数调用抽象方法，为它添加功能，而不是从头实现。@abstractmethod 装饰器用法参见 abc 文档
上面 inspect 方法实现的比较笨拙，不过却表明，有了 pick()，和 load(...) 方法，如果想查看 Tombola 内容，可以先把所有元素挑出，再放回去。这个例子的目的是强调抽象基类可以提供具体方法，只要依赖接口中其他方法就行。Tombola 具体子类知晓内部数据结构，可以覆盖 inspect() 方法，使用更聪明的方式实现
上面的 loaded() 方法看起来不笨，但是耗时间，调用 inspect() 方法构建有序元组只是为了看看序列是不是空。这样做可以，但是在子类做会更好，后文会讲到
注意，实现 inspect() 方法采用的是迂回方式捕获 pick() 方法抛出的 LookupError。pick() 抛出 LookupError 也是接口的一部分，但是在 Python 中无法声明，只能在文档说明
选择使用 LookupError 的原因是，在 Python 异常关系层次中，它是 IndexError 和 KeyError 的父类，这两个是具体实现 Tombola 所有的数据结构最有可能抛出的异常
为了看看抽象基类对接口做的检查，下面我们尝试使用一个有缺陷的实现糊弄 Tombola：
Step13:  抽象基类语法简介
声明抽象基类的最简单方式是继承 abc.ABC 或其他抽象基类。
然而，abc.ABC 是 Python 3.4 增加的新类，如果使用旧版 Python，无法继承现有抽象基类，必须用 metaclass= 关键字，把值设为 abc.ABCMeta（不是 abc.ABC）。
写成下面这样：
Step14:  metaclass= 关键字是 Python 3 引入的。在 Python 2 中必须使用 __metaclass__ 类属性：
Step15:  元类将在 21 章讲解，我们先将其理解为一种特殊的类，同样也把抽象基类理解为一种特殊的类。例如：“常规的”类不会检查子类，因此这是抽象基类的特殊行为
除了 @abstractmethod 之外，abc 模块还定义了 @abstractclassmethod, @abstractstaticmethodm, @abstractproperty 三个装饰器。然而，后 3 个装饰器从 Python 3.3 废弃了，因为装饰器可以在 @abstractmethod 上对叠，那三个就显得多余了。例如，生成是抽象类方法的推荐方式是：
Step16:  在函数上堆叠装饰器的顺序非常重要，@abstractmethod　文档就特别指出：
与其他描述符一起使用时，abstractmethod() 应该放在最里层．
也就是说，在 @abstractmethod 和 def 语句之间不能有其它装饰器
定义 Tombola 抽象基类的子类
定义好 Tombola 抽象基类之后，我们要开发两个具体子类，满足 Tombola 规定的接口。
下面的 BingoCage 类例子是根据第五章例子修改的，使用了更好的随机发生器。BingoCage 实现了所需的首相方法 load 和 pick
从 Tombola 中继承了 loaded 方法，覆盖了 inspect 方法，增加了 __call__ 方法。
Step17:  random.SystemRandom 使用 os.urandom(...) 函数实现 random API，根据 os 模块文档，这个函数生成“适合用于加密”的随即字节序列
BingoCage 从 Tombola 继承了耗时的 loaded 方法和笨拙的 inspect 方法。这两个方法都可以覆盖，变成下面例子中更快的方法，这里想表达的观点是：我们可以偷懒，直接从抽象基类中继承不是那么理想的具体方法。从 Tombola 中继承的方法没有 BingoCage 自己定义的那么快，不过只要 Tbombola 子类能正确的实现 pick 和 load 方法，就能提供正确的结果
下面是 Tombola 接口的另一种实现，虽然与之前不同，但是完全有效。LotteryBlower 打乱“数字球”后没有提取最后一个，而是提取一个随即位置上的球
Step18:  有个习惯做法值得指出，在 __init__ 方法中，self._balls 保存的是 list(iterable),而不是 iterable 的引用，这样会 LotterBlower 更灵活，因为 iterable 参数可以是任意可迭代的类型。把元素存入列表中还确保能取出元素。就算 iterable 参数始终传入列表，list(iterable) 会创建参数副本，这依然是好的做法，因为用户可能不希望自己提供的数据被改变
Tombola 的虚拟子类
白鹅类型的一个基本特性(也是值得用水禽来命名的原因）：即使不继承，也有办法把一个类注册为抽象基类的虚拟子类。这样做时，我们保证注册的类忠实地实现了抽象基类定义的接口，而 Python 会相信我们从不做检查。如果我们说谎了，那么常规运行时异常会把我们捕获
注册虚拟子类的方式是在抽象基类上调用 register 方法。这么做之后，注册的类会变成抽象基类的虚拟子类，而且 issubclass 和 isinstance 等函数都能识别，但是注册的类不会从抽象基类中继承任何方法或属性。
虚拟子类不会继承注册的抽象基类，而且任何时候都不会检查它是符合抽象基类的接口，即便在实例化时也不会检查。为了避免运行时错误，虚拟子类实现所需的全部方法
register 方法通常作为普通的函数调用，不过也可以作为装饰器使用。在下面的例子，我们使用装饰器语法实现了 TomboList 类，这是 Tombola 的一个虚拟子类
Step19:  注册之后，可以使用 issubclass 和 isinstance 函数判断 TomboList 是不是 Tombola 的子类
Step20:  然而，类的继承关系在一个特殊的类中指定 -- __mro__，即方法解析顺序（Method Resolution Order）。这个属性的作用域很简单，按顺序列出类及超类，Python 会按照这个顺序搜索方法。查看 TomboList 类的 __mro__ 属性，你会发现它只列出了 “真实的” 超类，即 list 和 object：
Step21:  Python 使用 register 的方式
Python 3.3 之前的版本不能将 register 当做装饰器使用，必须定义类以后像普通函数那样调用。
虽然现在可以当装饰器使用了，但是更常见的做法还是当函数，例如 collections.abc 模块源码中：
Sequence.register(tuple)
Sequence.register(str)
Sequence.register(range)
Sequence.register(memoryview)
鹅的行为可能像鸭子
Alex 讲故事时候说过，即使不注册，抽象基类也能把一个类识别成虚拟子类，下面是他举得一个例子，我添加了一些代码，用 issubclass 来测试：
Step22:  经过 issubclass 函数确认（isinstance 也会得到相同的结论），Struggle 是 abc.Sized 的子类，这是因为 abc.Sized 实现了一个特殊的方法， __subclasshook__。看下面 Sized 类的源码：
<ASSISTANT_TASK:>
Python Code:
class Vector2d:
    typecode = 'd'
    
    def __init__(self, x, y):
        self.x = float(x)
        self.y = float(y)
        
    def __iter__(self):
        return (i for i in (self.x, self.y))
Explanation: 本章讨论的话题是接口，从鸭子类型代表特征动态协议，到使接口更明确，能验证是否符合规定的抽象基类（Abstract Base Class，ABC）
在 Python 中 上章所说的鸭子类型是接口的常规方式，新只是是抽象基类和类型检查。Python 语言诞生 15 年之后，Python 2.6 才引入抽象基类。
本章先说明 Python 社区以往对接口的不严谨理解：部分实现接口通常被认为是可接受的。我们通过几个示例强调鸭子类型的动态本性，从而澄清这一点
接着，通过 Alex Martelili 写的一篇短文，对抽象基类作介绍，还为 Python 编程下一个新趋势下定义。本章余下的内容专门讲解抽象基类。首先，本章说明抽象基类的常见用途：实现接口时作为超类使用。
然后，说明抽象基类如何检查具体子类是否符合接口定义，以及如何使用注册机制声明一个类实现了某个接口，而不进行子类化操作。最后，说明如何让抽象基类自动 ”识别“ 任何符合接口的类 -- 不进行子类化或注册
我们将实现一个新抽象基类，看看它的运作方式。但是，作者和 Alex Martelli 都不建议你自己编写抽象基类，因为容易过度设计
抽象基类与描述符和元类一样，是用于构建框架的工具。因此，只有少数 Python 开发者编写的抽象基类不会对用户施加不必要的限制，让他们做无用功
下面从 Python 风格探索接口
Python 文化中的接口和协议
在引入抽象基类之前，Python 已经非常成功了，即使现在也很少有代码使用抽象基类。在第一章就讨论了鸭子类型和协议，在上一章，我们将协议定义为非正式的接口，是让 Python 这种动态类型语言实现多态的方式。
接口在动态类型语言是怎么运作的呢？首先，基本的事实是，Python 语言没有 interface 关键字，而且除了抽象基类，每个类都有接口：类实现或继承的公开属性（方法或数据的属性），包括特殊方法，如 __getitem__ 或 __add__ 
按照定义，受保护的属性和私有属性不在接口中：即便有“受保护”属性也只是采用命名约定实现的（单个前导下划线）私有属性也可以轻松的访问（第 9 章），原因也是如此，不要违背这些约定
另一方面，不要觉得把公开数据属性放入对象接口中不妥，因为如果需要，总能实现读值方法和设值方法，把数据属性变成特性，使用 obj.attr 语法的客户代码不会受到影响。Vector2d 类就是这么做的
下面的例子 x，y 是公开属性
End of explanation
class Vector2d:
    typecode = 'd'
    
    def __init__(self, x, y):
        self._x = float(x)
        self._y = float(y)
        
    @property
    def x(self):
        return self._x
    
    @property
    def y(self):
        return self._y
    
    def __iter__(self):
        return (i for i in (self.x, self.y))
Explanation: 在第 9 章中，我们将其变成了只读属性，这是重大的重构，但是 Vector2d 的接口基本没变，用户仍然能读取 my_vector.x 和 my_vector.y。下面是使用特性实现 x，y（第 9 章的代码）
End of explanation
class Foo:
    def __getitem__(self, pos):
        return range(0, 30, 10)[pos]
    
f = Foo()
f[1]
for i in f: print(i)
20 in f
15 in f
Explanation: 关于接口，这里有个实用的补充定义：对象公开方法的子集，让对象在系统中扮演特定的角色。Python 文档中的 “文件类对象” 或 “可迭代对象” 就是这个意思，这种说法指的不是特定的类。接口是实现特定角色的方法集合，这样理解正是 Smalltalk 程序员说的协议，其他动态预言社区都借鉴了这个术语，协议与继承没有关系。一个类可能会实现多个接口，从而让实例扮演多个角色
协议是接口，但不是正式的（只由文档和约定定义），因此协议不能像正式接口那样施加限制（本章后面会说明抽象基类对接口一致性的强制）。一个类可能只实现部分接口，这是允许的。有时，某些 API 只要求 “文件类对象” 返回字节序列 .read() 方法。在特定的上下文中可能需要其他文件操作方法，也可能不需要
作者写书时候，Python 3 中的 memoryview 的文档说，它能处理“支持缓冲协议的对象“，不过缓冲协议的文档是 C API 的。 bytearray 的构造方法接受”一个符合缓冲接口的对象”。如今，文档正在改变用词，使用“字节序列类对象”这样更加友好的表述。我指出这一点是为了强调，对 Python 程序员来说，'X' 类对象 和 'X' 协议和 'X' 接口都是一个意思
序列协议是 Python 是最基础的协议之一。即使对象只实现了那个协议的最基本的一部分，解释器也能负责任的处理，如下一节所示。
Python 喜欢序列
Python 数据模型的哲学是尽量支持基本协议。对序列来说，即便是最简单的实现，Python 也会力求做的最好
抽象基类 Sequence 的正式接口如下：__getitem__, __contains__, __iter__, __reversed__, index, count
下面的例子 Foo 类没有继承 abc.Sequence，而且只实现了序列协议的一个方法: __getitem__（没有实现 __len__ 方法）,看到这样足够访问元素，迭代和使用 in 运算符了
End of explanation
import collections
Card = collections.namedtuple('Card', ['rank', 'suit'])
class FrenchDeck:
    ranks = [str(n) for n in range(2, 11)] + list('JQKA')
    suits = 'spades diamonds clubs hearts'.split()
    
    def __init__(self):
        self._cards = [Card(rank, suit) for suit in self.suits
                                        for rank in self.ranks]
        
    def __len__(self):
        return len(self._cards)
    
    def __getitem__(self, position):
        return self._cards[position]
Explanation: 虽然没有 __iter__ 方法，但是 Foo 实例是可迭代对象，因为发现有 __getitem__ 方法时，Python 会调用它，传入从 0 开始的整数索引，尝试迭代对象（这是一种后备机制）。尽管没有实现 __contains__ 方法，但是 Python 足够只能，能迭代 Foo实例，因此也能使用 in 运算符：Python 会做全面检查，看看有没有指定的元素
综上，鉴于序列协议的重要性，如果没有 __iter__ 和 __contains__ 方法，Python 会调用 __getitem__ 方法，设法让迭代和 in 运算符可用
第一章定义的 FrenchDeck 类也没有继承 abc.Sequence，但是实现了序列协议的两个方法: __getitem__ 和 __len__。第一章那些示例之所以能用，大部分是由于 Python 会特殊对待看起来像序列的对象。Python 中迭代是鸭子类型的一种极端形式：为了迭代对象，解释器会尝试调用两个不同的方法
End of explanation
from random import shuffle
l = list(range(10))
shuffle(l)
l
Explanation: 下面分析一个示例，着重强调协议的动态本性
使用猴子补丁在运行时实现协议
在上面 FrenchDeck 类有一个重大的缺陷：无法洗牌。几年前，第一次编写 FrenchDeck 示例时，我发现了 shuffle 方法。后来。我对 Python 风格有了深刻理解，我发现如果 FrenchDeck 实例的行为像序列，那么就不需要 shuffle 方法，因为已经有 random.shuffle 函数可用，文档中说它的作用就是就地打乱序列
End of explanation
from random import shuffle
deck = FrenchDeck()
shuffle(deck)
Explanation: 然而，要打乱 FrenckDeck 函实例，会出现异常，如下所示：
End of explanation
def set_card(deck, position, card):
    deck._cards[position] = card
    
FrenchDeck.__setitem__ = set_card
shuffle(deck) # 现在可以打乱顺序了，因为实现了可变序列协议所需要的方法
deck[:5]
Explanation: 错误消息很明确，FrenchDeck 对象不支持为元素赋值。这个问题的原因是，FrenchDeck 只实现了不可变的序列协议，可变序列还需要提供 __setitem__ 方法。
Python 是动态语言，因此我们可以在运行时修正这个问题，甚至还可以在交互式控制台中，修正方法如下所示：
End of explanation
class Aritist:
    def draw(self):
        pass
        
class Gunslinger:
    def draw(self): 
        pass
        
class Lottery:
    def draw(self):
        pass
Explanation: __setitem__ 在语言参考中使用的参数是 self, key, value，我们这里用的是 deck, position 和 card，这是为了提醒我们，每个 Python 方法说到底就是一个普通的函数，把第一个参数命名为 self 是一种约定。在控制台会话使用那几个参数没问题，但是在 Python 源码文件中最好按照文档那样使用 self, key, value
这里的关键是，set_card 函数要知道 deck 对象有一个名为 _cards 的属性，而且 _cards 必须是可变序列，然后吧 setcard 方法赋给特殊方法 __setitem__，从而把它依附到 FrenchDeck 类上。这种技术叫猴子补丁：在运行时修改类或模块，而不改动源码，猴子补丁很强大，但是打补丁的代码与要补丁的程序耦合十分紧密，而且往往要处理隐藏和没有文档的部分
除了举例的猴子补丁之外，上面例子还强调了协议是动态的：random.shuffle() 函数不关心参数类型，只要那个对象实现了部分可变序列协议即可。即便对象一开始没有所需的方法也没关系，后来再提供也行
目前，本章讨论的主题是 “鸭子类型”：对象的类型无关紧要，只要实现了特定的协议即可。
前面给出的抽象基类 Sequence 是为了展示协议与抽象基类的文档中所说的接口之间的关系，但是目前为止还没有真正的实现抽象基类
在下面的几节中，我们将直接使用抽象基类，而不是将其视为文档
Alex Martelli 的水禽
Alex Martelli 讲故事：
wiki 说我协助传播了“鸭子类型”这种言简意赅的说法（即斛律对象的真正类型，转而关注对象有没有实现所需的方法，签名和语义）
对 Python 来说，这基本上指避免使用 instance 检查对象类型(更别提 type(foo) is bar 这种更糟糕的检查方式了，这样做没有任何好处，甚至禁止最简单的继承方式）。
总的来说，鸭子类型很有用，但是有的时候继承的方式更好。
在生物学中，平行进化会导致不相关的种类产生相似的属性，形态和举止方面都是如此，但是生态位的相似性是偶然的，不同的种类仍然属于不同的生态位。编程语言也有这种“偶然的相似性”，比如下面经典的面向对象编程示例：
End of explanation
class Struggle:
    def __len__(self): return 23
    
from collections import abc
isinstance(Struggle(), abc.Sized)
Explanation: 显然，因为 x，y 两个对象刚好都有一个名为 draw 的方法，而且调用时候不用传入参数，即 x.draw() 和 y.draw()，远远不能确保二者可以相互调用，或者具有相同的抽象。也就是说，从这样的调用中不能推导出语义的相似性。相反，我们需要一位渊博的程序员主动把这种等价维持在一定层次上。
例如，草雁（以前认为与其他鹅类比较类似）和麻鸭（以前认为与其他鸭类比较类似）现在被分到 Tadornidae 亚科（表明二者相似性比鸭科其他动物高，因为他们的共同祖先比较接近）。此外，DNA 分析表明，白翅木鸭与美洲家鸭（属于麻鸭）不是很像，至少没有形态和举止看起来那么像，因此把木鸭单独分成了一组，完全不再 Tadornidae 亚科中。
知道这些有什么用呢？视情况而定，比如，逮到一直水禽之后，决定如何烹制才最美味时，显著的特征（不是全部，例如一身羽毛并不重要）主要是口感和风味（过时的表征学），这比支序学重要的多。但是其他方面，如对不同病原体的抗性（圈养水禽还是放养），DNA 接近性的作用就大多了。
因此，参照水禽的分类学演化，我建议在鸭子类型基础上增加白鹅类型（goose typing)。
白鹅类型是指，只要 cls 是抽象基类，即 cls 元类是 abcABCMeta 就可以使用 isinstance(obj, cls)。
colections.abc 中有很多有用的抽象基类（Python 标准库的 numbers 模块还有一些）。
与具体类相比，抽象基类有很多理论上的优点，Python 抽象基类还有一个重要的实用优势：可以使用 register 类方法在终端用户的代码把某个类 “声明”  为一个抽象基类的 “虚拟” 子类（为此，被注册的类必须满足抽象基类对方法名称和签名的要求，最重要的是要满足底层语义契约;但是，开发那个类时不用了解抽象基类，更不用继承抽象基类）。这大大打破了严格的强耦合，与面向对象编程人员掌握的知识有很大出入，因此使用继承时要小心
有时，为了让抽象基类识别子类，甚至不用注册。
其实，抽象基类的本质就是几个特殊方法。例如：
End of explanation
field_names = ['kaka,hello,world', "test, test2"]
try:
    field_names = field_names.replace(',', ' ').split() # 逗号换成空格并拆分成列表
except AttributeError:
    pass
field_names = tuple(field_names) # 为了确保传进去的是可迭代对象，也为了创建一个备份，使用所得值创建一个元组
field_names
Explanation: 可以看出，无需注册，abc.Sized 也能把 Struggle 识别为自己的子类，只要实现了特殊方法 __len__ 即可（要使用正确的语法和语义实现，前者要求没有参数，后者要求返回一个非负整数，指明对象的长度;如果不使用规定的语法和语义实现 __len__ 方法，会导致非常严重的问题）
最后要说的是：如果实现的类具体实现了 numbers, collections.abc 或者其他框架中的抽象基类的概念，要么继承相应的抽象基类（必要时），要么类注册到相应的抽象基类中。开始开发程序时，不要使用提供注册功能的库或框架，要自己动手注册，如果必须检查参数的类型（这是最常见的），例如检查是不是 “序列”，那么就这么做：
isinstance(the_arg, collections.abc.Sequence)
此外，不要在生产代码中定义抽象基类（或元类）。。。如果你很想这样做，我打赌你可能要找茬（= =），刚拿到新工具的人都有大干一场的冲动。如果你能避开这些深奥的概念，你（以及未来的代码维护者）的生活将更加愉快，因为代码会变得简洁明了，讲完啦～
除了提出 ”白鹅类型“ 之外，Alex 还指出，继承抽象基类很简单，只需要实现所需的方法，这样也能明确表明开发者意图，这一意图还能通过注册虚拟子类实现。
此外，使用 isinstance 和 issubclass 测试抽象基类更为人接受，过去，这两个函数用来测试鸭子类型，但用于抽象基类会更加灵活。毕竟，如果某个组件没有继承抽象基类，事后还可以注册，让显式类型检查通过
然而即使是抽象基类，也不能滥用 isinstance 检查，用多了可能导致代码很糟糕。在一连串 if/elif/elif 中使用 isinstance 做检查，然后根据对象类型做不同操作，是十分糟糕的做法，此时应该使用堕胎，即使用一定的方式定义类，让解释器把调用分派给正确的方法，而不是采用 if/elif/elif 块硬编码分派逻辑
具体使用时，上述建议有一个常见的例外：有些 Python API 接受一个字符串或字符串序列，如果只有一个字符串，可以把它放到列表中，从而简化处理。因为字符串是序列类型，所以为了把他和其它不可变序列区分，最简单的方式是使用 isinstance(x, str) 检查
可惜，Python 3.4 没有能把字符串和元组或者其他不可变序列区分开的抽象基类，因此必须测试 str。在 Python 2 中，basestr 类型可以协助这样的测试。basestr 不是抽象基类，但是他是 str 和 unicode 的超类。然而，Python 3 却把 basestr 去掉了，奇怪的是，Python 3 中有个 collections.abc.ByteString 类型，但是只能检测 byte 和 bytearray 类型
另一方面，如果必须强制执行 API 契约，通常可以使用 isinstance 检查抽象基类。“老兄，如果你想调用我，必须实现这个”，正如本书审校 Lennart Regebro 所说。这对采用插入式架构的系统来说特别有用。在框架之外，鸭子类型通常比类型检查更简单灵活
例如，本书有几个示例要使用序列，把他当成列表，我没有检查参数的类型是不是 list，而是直接接受参数，立即使用它构建一个列表。这样，我就可以接受任何可迭代对象，如果参数不是可迭代对象，调用立即失败，并提供清晰的错误信息，本章后面有一个这样的例子。当然，如果序列太长或者需要就地修改序列而导致无法复制参数，就不能采用这种方式，此时，使用 isinstance(x, abc.MutableSequence) 更好，如果可以接受任何可迭代对象，也可以调用 iter(x) 函数获得一个迭代器，第 14 章详细讲
模仿 collections.namedtuple 处理 field_names 参数的方式也是一例，field_names 的值可以是单个字符串，以空格或者逗号分隔符，也可以是一个标识符序列，此时可能想使用 isinstance，但是我会使用鸭子类型，如下所示：
使用鸭子类型处理单个字符串或者由字符串组成的可迭代对象：
End of explanation
import collections
Card = collections.namedtuple('Card', ['rank', 'suit'])
class FrenchDeck2(collections.MutableSequence):
    ranks = [str(n) for n in range(2, 11)] + list('JQKA')
    suits = 'spades diamonds clubs hearts'.split()
    
    def __init__(self):
        self._cards = [Card(rank, suit) for suit in self.suits
                                        for rank in self.ranks]
    
    def __len__(self):
        return len(self._cards)
    
    def __getitem(self, position):
        return self._cards[position]
    
    def __setitem(self, position, value): # 为了支持洗牌
        self._cards[position] = value
        
    def __delitem__(self, position):      # 继承 MutableSequence 必须实现 __delitem__ 方法，这是它的一个抽象方法
        del self._cards[position]
        
    def insert(self, position, value):    # insert 方法也是 MutableSequence 类的第三个方法
        self._cards.insert(position, value)
Explanation: 最后再 Alex Martelli 强调一下建议尽量不要自己实现抽象基类，容易造成灾难性后果，下面通过实例讲解白鹅类型：
定义抽象基类的子类
我们按照 Martelli 的建议，先利用现有抽象基类（collections.MutableSequence)，然后再自己定义。在下面例子，我们将 FrenchDeck2 声明为collections.MutableSequence 的子类
End of explanation
import abc
class Tombola(abc.ABC):
    @abc.abstractmethod
    def load(self, iterable):
        '''从可迭代对象中添加元素'''
        
    @abc.abstractmethod # 抽象方法使用此标记
    def pick(self):
        '''随机删除元素，然后将其返回
           如果实例为空，这个方法抛出 LookupError
        '''
        
    def loaded(self):
        '''如果至少有一个元素，返回 True，否则返回 False'''
        return bool(self.inspect()) # 抽象基类中的具体方法只能依赖抽象基类定义的接口（即只能使用抽象基类的其他具体方法，抽象方法或特性）
    
    def inspect(self):
        '''返回一个有序元组，由当前元素构成'''
        items = []
        while 1:  # 我们不知道具体子类如何存储元素，为了得到 inspect 结果，不断调用 pick 方法，把 Tombola 清空
            try:
                items.append(self.pick())
            except LookupError:
                break
        self.load(items)  # 再加回去元素
        return tuple(sorted(items))
Explanation: Python 不会在模块导入时候检查抽象方法的实现，而是在实例化 FrenchDeck2 类时才会真正的检查。因此，如果没有正确实现某个抽象方法，Python 会抛出 TypeError 异常，并把错误消息设为 “Can't instantiate abstract class FrenchDeck2 with abstract methods __delitem__, insert"。正是这个原因，即使 FrenchDeck2 类不需要 __delitem__ 和 insert 提供的行为，但是因为继承 MutableSequence 抽象基类，必须实现他们。
FrenchDeck2 从 Sequence（因为 MultableSequence 继承了 Sequence） 继承了几个拿来即用的具体方法 __contains__, __iter__, __reversed__, index 和 count。FrenchDeck2 从 MutableSequence 继承了 append，extend，pop，remove 和 __iadd__
在 collections.abc 中，每个抽象基类的具体方法都是作为类的公开接口实现的，因此不用知道实例的内部接口
要想实现子类，可以覆盖抽象基类中继承的方法，以更高效的方式实现，例如，__contains__ 方法会全面扫描序列，如果面门定义的序列按顺序保存元素，就可以重新实现 __contain__ 方法，使用 bisect 函数做二分查找，提高速度
标准库中的抽象基类
Python 2.6 开始，标准库提供了抽象基类，大多数抽象基类在 collections.abc 模块中定义，不过其他地方也有，例如 numbers 和 io 包中包含一些，但是 collections.abc 中的抽象基类最常用，我们看看这个模块有那些抽象基类
标准库有两个 abc 模块，我们讨论的是 collections.abc 为了减少加载时间， Python 3.4 在 collections 包之外实现了这个模块，因此要与 cloections 分开导入。另一个 abc 模块就是 abc，这里定义的是 abc.ABC 类，每个抽象基类都依赖这个类，但是不用导入他，除非重新定义新的抽象基类
下面介绍几个抽象基类：
Iterable, Container, Sized
每个集合都应该继承这 3 个抽象基类，或者至少要实现兼容协议。Iterable 通过 __iter__ 方法支持迭代，Ccontainer 通过 __contains__ 方法支持 in 运算符，Sized 通过 __len__ 方法支持 len 函数
Sequence, Mapping 和 Set
这三个是主要的不可变集合类型，而且各自都有可变的子类。
MappingView
在 Python 3 中，映射方法 .items(), .keys(), .values() 返回的对象分别时 ItemsView，KeysView 和 ValuesView 的实例。前两个类还从 Set 类继承了丰富的接口，包含第 3 章所述的所有运算符
Callable 和 Hashable 
这两个抽象基类和集合没有太大关系，只不过因为 collections.abc 是标准库中定义抽象基类的第一个模块，而他们又太重要了，所以才放到 collections.abc 模块中。我从未见过 Callable 或 Hashable 的子类。这两个抽象基类的主要作用是为内置函数 isinstance 提供支持提供支持，以一种安全的方式判断对象能不能调用或三裂
若检查是否能调用，可以使用内置的 callable() 函数，但是没有类似的 hashable() 函数，因此测试对象是否可散列，最好使用 isinstance(my_obj, Hashable)
Iterator
注意它是 Iterable 的子类，我们在第 14 章继续讨论
继 collections.abc 之后，标准库中最有用的抽象基类包是 numbers，我们来介绍一下
抽象基类的数字塔
numbers 包定义的是 “数字塔”（即各个抽象基类的层次结构是线性的），其中 Number 是位于最顶端的超类，随后是 Complex，依次往下，最底端是 Integral 类
Number
Complex
Real
Rational
Integral
因此，要检查一个数是不是整数，可以使用 isinstance(x, numbers.Integral)，这样代码就能接受 int, bool（inti 的子类），或者外部库使用 numbers 抽象基类注册的其他类型。为了满足检查需要，你或者你的 API 用户始终可以把兼容的类型注册为 numbers.Integral 的虚拟子类
与之类似，如果一个值可能是浮点数类型，可以使用 isinstance(x, numbers.Real) 检查。这样代码就能接受 bool，int，float，fractions.Fraction 或者外部库（如 Numpy，它做了相应注册）提供非复数的类型
decimal.Decimal 没有注册为 numbers.Real 的虚拟子类，这有写奇怪。没注册的原因时，如果你的程序需要 Decimal 的精度，要防止与其他低精度的数字类型混淆，尤其是浮点数
了解一些现有的抽象基类之后，我们将从零开始实现一个抽象基类，然后开始使用，以此实现白鹅类型。这么做的目的不是鼓励每个人都立即定义抽象基类，而是教你怎么阅读标准库和其他包中的抽象基类源码。
定义并使用一个抽象基类
假如我们要在网站随机显示广告，但是在整个广告清单轮转一遍，不重复显示广告。我们正在构建一个广告管理框架，名叫 ADAM，它的职责之一是，支持用户随机挑选无重复类，我们将为此定义一个抽象基类
收到栈和队列启发，我们将使用现实中物体命名这个抽象基类：宾果机和彩票机是随机从有限集合挑选物品的机器，选出的物品没有重复，直到选完为止
我们将这个抽象基类命名为 Tombola，这是宾果机和打乱数字的滚动容器的意大利名字
Tombla 抽象基类有四个方法，其中两个是抽象方法。
.load(...) 把元素放入容器
.pick() 从容器中随机拿出一个元素，返回选中的元素
下面两个是具体方法：
.loaded() 如果容器中至少有一个元素，返回 True
.inspect() 返回一个有序元组，由容器现有元素构成，不会修改容器内容
抽象基类的定义如下所示：
End of explanation
class Fake(Tombola):
    def pick(self):
        return 13
Fake
f = Fake()  #实例化的时候会报错
Explanation: 其实，抽象方法也可有实现代码，即使实现了，子类也必须覆盖抽象方法，但是在子类中可以用 super() 函数调用抽象方法，为它添加功能，而不是从头实现。@abstractmethod 装饰器用法参见 abc 文档
上面 inspect 方法实现的比较笨拙，不过却表明，有了 pick()，和 load(...) 方法，如果想查看 Tombola 内容，可以先把所有元素挑出，再放回去。这个例子的目的是强调抽象基类可以提供具体方法，只要依赖接口中其他方法就行。Tombola 具体子类知晓内部数据结构，可以覆盖 inspect() 方法，使用更聪明的方式实现
上面的 loaded() 方法看起来不笨，但是耗时间，调用 inspect() 方法构建有序元组只是为了看看序列是不是空。这样做可以，但是在子类做会更好，后文会讲到
注意，实现 inspect() 方法采用的是迂回方式捕获 pick() 方法抛出的 LookupError。pick() 抛出 LookupError 也是接口的一部分，但是在 Python 中无法声明，只能在文档说明
选择使用 LookupError 的原因是，在 Python 异常关系层次中，它是 IndexError 和 KeyError 的父类，这两个是具体实现 Tombola 所有的数据结构最有可能抛出的异常
为了看看抽象基类对接口做的检查，下面我们尝试使用一个有缺陷的实现糊弄 Tombola：
End of explanation
#class Tombola(metaclass=abc.ABCMeta):
#    pass
Explanation: 抽象基类语法简介
声明抽象基类的最简单方式是继承 abc.ABC 或其他抽象基类。
然而，abc.ABC 是 Python 3.4 增加的新类，如果使用旧版 Python，无法继承现有抽象基类，必须用 metaclass= 关键字，把值设为 abc.ABCMeta（不是 abc.ABC）。
写成下面这样：
End of explanation
#class Tombola(object): # Python 2
#    __metaclass__ = abc.ABCMeta
#    pass
Explanation: metaclass= 关键字是 Python 3 引入的。在 Python 2 中必须使用 __metaclass__ 类属性：
End of explanation
# import abc
# class MyABC(abc.ABC):
#     @classmethod
#     @abc.abstractmethod
#     def an_abstract_classmethod(cls, ...):
#         pass
Explanation: 元类将在 21 章讲解，我们先将其理解为一种特殊的类，同样也把抽象基类理解为一种特殊的类。例如：“常规的”类不会检查子类，因此这是抽象基类的特殊行为
除了 @abstractmethod 之外，abc 模块还定义了 @abstractclassmethod, @abstractstaticmethodm, @abstractproperty 三个装饰器。然而，后 3 个装饰器从 Python 3.3 废弃了，因为装饰器可以在 @abstractmethod 上对叠，那三个就显得多余了。例如，生成是抽象类方法的推荐方式是：
End of explanation
import random
class BingoCage(Tombola):
    
    def __init__(self, items):
        self._randomizer = random.SystemRandom()
        self._items = []
        self.load(items)
        
    def load(self, items):
        self._items.extend(items)
        self._randomizer.shuffle(self._items)
        
    def pick(self):
        try:
            return self._items.pop()
        except IndexError:
            raise LookupError('pick from empty BingoCage')
            
    def __call__(self):
        self.pick()
Explanation: 在函数上堆叠装饰器的顺序非常重要，@abstractmethod　文档就特别指出：
与其他描述符一起使用时，abstractmethod() 应该放在最里层．
也就是说，在 @abstractmethod 和 def 语句之间不能有其它装饰器
定义 Tombola 抽象基类的子类
定义好 Tombola 抽象基类之后，我们要开发两个具体子类，满足 Tombola 规定的接口。
下面的 BingoCage 类例子是根据第五章例子修改的，使用了更好的随机发生器。BingoCage 实现了所需的首相方法 load 和 pick
从 Tombola 中继承了 loaded 方法，覆盖了 inspect 方法，增加了 __call__ 方法。
End of explanation
import random
class LotteryBlower(Tombola):
    def __init__(self, iterable):
        self._balls = list(iterable)
        
    def load(self, iterable):
        self._balls.extend(iterable)
        
    def pick(self):
        try:
            position = random.randrange(len(self._balls))
        except ValueError:
            # 为了兼容 Tombola，我们抛出 LookupError
            raise LookupError('pick from empty LotteryBlower')
        return self._balls.pop(position)
    
    def loaded(self):
        return bool(self._balls)
    
    def inspect(self):
        return tuple(sorted(self._balls))
Explanation: random.SystemRandom 使用 os.urandom(...) 函数实现 random API，根据 os 模块文档，这个函数生成“适合用于加密”的随即字节序列
BingoCage 从 Tombola 继承了耗时的 loaded 方法和笨拙的 inspect 方法。这两个方法都可以覆盖，变成下面例子中更快的方法，这里想表达的观点是：我们可以偷懒，直接从抽象基类中继承不是那么理想的具体方法。从 Tombola 中继承的方法没有 BingoCage 自己定义的那么快，不过只要 Tbombola 子类能正确的实现 pick 和 load 方法，就能提供正确的结果
下面是 Tombola 接口的另一种实现，虽然与之前不同，但是完全有效。LotteryBlower 打乱“数字球”后没有提取最后一个，而是提取一个随即位置上的球
End of explanation
from random import randrange
@Tombola.register #注册虚拟子类
class TomboList(list): #继承 list
    def pick(self):
        if self: # 从 list 继承 __bool__ 方法，列表不为空时候返回 True
            position = randrange(len(self))
            return self.pop(position)    #调用继承自 list 的 pop 方法
        else:
            raise LooupError('pop from empty TomboList')
            
    load = list.extend # Tombolist.load  和 list.extend 一样
    
    def loaded(self):
        return bool(self)
    
    def inspect(self):
        return tuple(sorted(self))
    
#Tombola.register(TomboList) # Python 3.3 之前不能把 register 当做类装饰器使用，必须使用标准的调用语法
Explanation: 有个习惯做法值得指出，在 __init__ 方法中，self._balls 保存的是 list(iterable),而不是 iterable 的引用，这样会 LotterBlower 更灵活，因为 iterable 参数可以是任意可迭代的类型。把元素存入列表中还确保能取出元素。就算 iterable 参数始终传入列表，list(iterable) 会创建参数副本，这依然是好的做法，因为用户可能不希望自己提供的数据被改变
Tombola 的虚拟子类
白鹅类型的一个基本特性(也是值得用水禽来命名的原因）：即使不继承，也有办法把一个类注册为抽象基类的虚拟子类。这样做时，我们保证注册的类忠实地实现了抽象基类定义的接口，而 Python 会相信我们从不做检查。如果我们说谎了，那么常规运行时异常会把我们捕获
注册虚拟子类的方式是在抽象基类上调用 register 方法。这么做之后，注册的类会变成抽象基类的虚拟子类，而且 issubclass 和 isinstance 等函数都能识别，但是注册的类不会从抽象基类中继承任何方法或属性。
虚拟子类不会继承注册的抽象基类，而且任何时候都不会检查它是符合抽象基类的接口，即便在实例化时也不会检查。为了避免运行时错误，虚拟子类实现所需的全部方法
register 方法通常作为普通的函数调用，不过也可以作为装饰器使用。在下面的例子，我们使用装饰器语法实现了 TomboList 类，这是 Tombola 的一个虚拟子类
End of explanation
issubclass(TomboList, Tombola)
t = TomboList(range(100))
isinstance(t, Tombola)
Explanation: 注册之后，可以使用 issubclass 和 isinstance 函数判断 TomboList 是不是 Tombola 的子类
End of explanation
TomboList.__mro__
Explanation: 然而，类的继承关系在一个特殊的类中指定 -- __mro__，即方法解析顺序（Method Resolution Order）。这个属性的作用域很简单，按顺序列出类及超类，Python 会按照这个顺序搜索方法。查看 TomboList 类的 __mro__ 属性，你会发现它只列出了 “真实的” 超类，即 list 和 object：
End of explanation
class Struggle:
    def __len__(self): return 23
    
from collections import abc
isinstance(Struggle(), abc.Sized)
issubclass(Struggle, abc.Sized)
Explanation: Python 使用 register 的方式
Python 3.3 之前的版本不能将 register 当做装饰器使用，必须定义类以后像普通函数那样调用。
虽然现在可以当装饰器使用了，但是更常见的做法还是当函数，例如 collections.abc 模块源码中：
Sequence.register(tuple)
Sequence.register(str)
Sequence.register(range)
Sequence.register(memoryview)
鹅的行为可能像鸭子
Alex 讲故事时候说过，即使不注册，抽象基类也能把一个类识别成虚拟子类，下面是他举得一个例子，我添加了一些代码，用 issubclass 来测试：
End of explanation
# class Sized(metaclass = ABCMeta):
    
#     __slots__ = ()
    
#     @abstractmethod
#     def __len__(self):
#         return 0
    
#     @classmethod
#     def __subclasshook__(cls, C):
#         if cls is Sized:
#             # 对于 C 类以及其超类，如果 `__dict__` 属性中名为 `__len__` 的属性。。。
#             if any("__len__" in B.__dict__ for B in C.__mro__): 
#                 return True # 返回 True，表明 C 是 Sized 的虚拟子类
#         return NotImplemented #否则，返回 NotImplement，让子类检查
Explanation: 经过 issubclass 函数确认（isinstance 也会得到相同的结论），Struggle 是 abc.Sized 的子类，这是因为 abc.Sized 实现了一个特殊的方法， __subclasshook__。看下面 Sized 类的源码：
End of explanation
<END_TASK>