第四部分第11章,接口:从协议到抽象基类(重点讲抽象基类)

  1. 接口就是实现特定角色的方法集合。
  2. 严格来说,协议是非正式的接口(只由文档约束),正式接口会施加限制(抽象基类对接口一致性的强制)。
  3. 在Python中,“X类对象”、“X协议”、“X接口”都是一个意思。如“文件类对象”、“可迭代对象”,指的不是特定的类。
  4. 一个类可能会实现多个接口,从而让实例扮演多个角色。
  5. Python语言没有interface关键字,而且除了抽象基类,每个类都有接口:类实现或继承的公开属性(方法或数据属性),包括特殊方法,如__getitem__或__add__。受保护的属性和私有属性不在接口中:即便是“受保护的”属性也只是采用命名约定实现的(单个前导下划线,可以轻松访问)。不要违背这些约定。

1. Python喜欢序列

例子1. 只实现序列协议的一部分,即只定义__getitem__,但这样足够访问元素、迭代和使用in运算符。

鉴于序列协议很重要,如果没有__iter__和__contains__方法,Python会调用__getitem__方法,设法让迭代和in运算符可用。

class Foo:
def __getitem__(self, pos):
return range(0, 30, 10)[pos] f = Foo()
# 1. f[]调用__getitem__
print(f[1])
# 2. 没有__iter__方法,转而调用__getitem__方法,传入从0开始的整数索引,尝试迭代对象(这是一种后备机制)
for i in f:
print(i)
# 3. 没有__contains__方法,转而调用__getitem__方法
print(20 in f)

Python中的迭代是鸭子类型的一种极端形式:为了迭代对象,解释器会尝试调用两个不同的方法。

2. 使用猴子补丁在运行时实现协议

2.1 如果遵守既定协议,很有可能增加利用现有的标准库的可能性,得益于鸭子类型。例如FrenchDeck实例的行为像序列,就可以用random.shuffle就地地打乱序列。

2.2 猴子补丁:在运行时修改类或模块,而不改动源码。

2.3 例如在FrenchDeck使用猴子补丁之前,虽然部分行为表现得像序列,但仍然不能使用random.shuffle(错误提示为'FrenchDeck' object does not support item assignment)。原因是shuffle函数要调换集合中元素的位置,而FrenchDeck只实现了不可变的序列。可变的序列还必须提供__setitem__方法。

2.4 在运行时修正FrenchDeck为可变序列,让random.shuffle能处理

def set_card(deck, position, card):
deck._cards[position] = card #猴子补丁,把自定义的函数赋值给__setitem__,把它变成可变的。
FrenchDeck.__setitem__ = set_card

2.5 猴子补丁缺点:打补丁的代码与要打补丁的程序耦合十分紧密,而且往往要处理隐藏和没有文档的部分。(例如这里的关键是set_card函数要知道deck对象有一个名为_cards属性,而且_cards的值必须是可变序列)

2.6 还要注意的是:

3. 直接使用抽象基类,而不只将其当作文档。

3.1 Python的抽象基类有一个重要的实用优势:可以实用register类方法在终端用户的代码中把某个类“声明“为一个抽象基类的”虚拟“子类。做register时,我们保证注册的类忠实地实现了抽象基类定义的接口,而Python会相信我们,从而不做检查。如果我们说谎了,那么常规的运行时异常会把我们捕获。(注册的类不会从抽象基类中继承任何方法或属性)

3.2 有时,为了让抽象基类识别子类,甚至不用注册。无需注册,abc.Sized也能把Struggle识别为自己的子类,只要实现了特殊方法__len__即可。

class Struggle:
def __len__(self):
return 23 from collections import abc
#True
print(isinstance(Struggle(), abc.Sized))

3.3 检查是不是”序列“,那就这样做:

isinstance(the_arg, collections.abc.Sequence)

3.4 最好避免自定义抽象基类。如果自己想创建新的抽象基类,先试着通过常规的鸭子类型来解决问题。

3.5 colletions.namedtuple处理field_names参数的原理

field_names的值可以是单个字符串,以空格或者逗号分隔标识符,也可以是一个标识符序列。

例子3.5.1 用鸭子类型处理单个字符串或由字符串组成的可迭代对象

#1. 假设是单个字符串风格(EAFP风格)
try:
#2. 把逗号替换成空格,然后拆分成名称列表。
field_names =
field_names.replace(',', ' ').split()
#3. 如果field_names看起来不像是字符串
except AttributeError:
#4. 假设已经是由名称组成的可迭代对象
pass
#5. 为了确保的确是可迭代对象,也为了保存一份副本,使用所得值创建一个元组。
field_names = tuple(field_names)

4. !继承抽象基类

例子1. 继承抽象基类,抽象基类会对子类作出限制。



导入时(加载并编译.py模块时),Python不会检查抽象方法的实现。在运行时实例化子类才会真正检查。

例子2. 假如我有一个FrenchDeck2的类继承MutableSequence抽象基类。



例子3. 我们可以覆盖子类中的从抽象基类中继承的方法,以更高效的方式重新实现。

例如,__cotains__方法会全面扫描序列。如果你定义的序列按顺序保存元素,那就可以重新定义__cotains__方法,使用bisect函数做二分查找,从而提升搜索速度。

5. !标准库中的抽象基类

collections.abc中的抽象基类最常用。numbers和io包中也有一些抽象基类。

5.1 collections.abc模块中的抽象基类

  1. 官方文档:https://docs.python.org/3/library/collections.abc.html
  2. 简要的UML类图(没有属性名称)





5.2 numbers的抽象基类

  1. https://docs.python.org/3/library/numbers.html
  2. numbers包定义的是”数字塔“(各个抽象基类的层次结构是线性的),其中Number是位于最顶端的超类,依次往下,最底端的是Integral类。

    Number、Complex、Real、Rational、Integral.
  3. 因此,如果想检查一个数是不是整数,可以使用isinstance(x, numbers.Integral),

6. 定义并使用一个抽象基类

  1. 最好不要自定义抽象基类
  2. 此处自定义抽象基类的目的是学会怎么阅读标准库和其他包中的抽象基类源码。
  3. 中文电子书P491

7. Python使用register

  1. 当装饰器用:@Sequence.register
  2. 当作函数用:Sequence.register(tuple)

8. 鹅(本章提到了goose typing)的行为有可能像鸭子

  1. 即便不注册,抽象基类也能把一个类别识别为虚拟子类。
class Struggle:
def __len__(self):
return 23 from collections import abc
isinstance(Struggle(), abc.Sized)
issubclass(Struggle, abc.Sized)

原理:



Fluent_Python_Part4面向对象,11-iface-abc,协议(接口),抽象基类的更多相关文章

  1. 【Python】【元编程】【从协议到抽象基类】

    """class Vector2d: typecode = 'd' def __init__(self,x,y): self.__x = float(x) self.__ ...

  2. Python 接口:从协议到抽象基类

    p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica } 抽象基类的常见用途:实现接口时作为超类使用.然后,说明抽象基类如何检查 ...

  3. python 用abc模块构建抽象基类Abstract Base Classes

    见代码: #!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2018/08/01 16:58 from abc import ABCMet ...

  4. Python抽象基类:ABC谢谢你,因为有你,温暖了四季!

    Python抽象基类:ABC谢谢你,因为有你,温暖了四季! Python抽象基类:ABC谢谢你,因为有你,温暖了四季! 实例方法.类方法和静态方法 抽象类 具名元组 参考资料 最近阅读了<Pyt ...

  5. python3:iterable, iterator, generator,抽象基类, itertools的使用。

    目录: iterable对象 iterator对象, 数据类型Iterator类 数据类型Generator类. 生成器表达式 collections.abc:容器的抽象基类.用于判断具体类. ite ...

  6. Python中的抽象基类

    1.说在前头 "抽象基类"这个词可能听着比较"深奥",其实"基类"就是"父类","抽象"就是&quo ...

  7. 6、面向对象以及winform的简单运用(抽象基类与接口)

    抽象类与抽象方法 1.书写规范: 在类前面加上abstract关键字,就成为了抽象类:在一个方法前面加上abstract关键字,就成为了抽象方法(抽象方法不能有实现方法,直接在后面加分号) 例: ab ...

  8. NSObject class和NSObject protocol的关系(抽象基类与协议)

    [转载请注明出处] 1.接口的实现 对于接口这一概念的支持,不同语言的实现形式不同.Java中,由于不支持多重继承,因此提供了一个Interface关键词.而在C++中,通常是通过定义抽象基类的方式来 ...

  9. PythonI/O进阶学习笔记_3.2面向对象编程_python的继承(多继承/super/MRO/抽象基类/mixin模式)

    前言: 本篇相关内容分为3篇多态.继承.封装,这篇为第二篇 继承. 本篇内容围绕 python基础教程这段: 在面向对象编程中,术语对象大致意味着一系列数据(属性)以及一套访问和操作这些数据的方法.使 ...

随机推荐

  1. HTML的图像标签

    网页的图像标签 常见的图像格式 JPG GIF PNG BMP 图像标签可以带属性,格式为: <img src="path" alt="text" tit ...

  2. 图的dfs遍历模板(邻接表和邻接矩阵存储)

    我们做算法题的目的是解决问题,完成任务,而不是创造算法,解题的过程是利用算法的过程而不是创造算法的过程,我们不能不能陷入这样的认识误区.而想要快速高效的利用算法解决算法题,积累算法模板就很重要,利用模 ...

  3. 2.17NOIP模拟赛(by hzwer) T1 小奇挖矿

    [题目背景] 小奇要开采一些矿物,它驾驶着一台带有钻头(初始能力值 w)的飞船,按既定 路线依次飞过喵星系的 n 个星球. [问题描述] 星球分为 2 类:资源型和维修型. 1. 资源型:含矿物质量 ...

  4. Dreamoon and WiFi

    Dreamoon is standing at the position 0 on a number line. Drazil is sending a list of commands throug ...

  5. crontab实践

    1.crontab概要 2.crontab使用 3.关键配置信息 3.1如何配置定时任务 4.注意事项 参考 https://www.cnblogs.com/keithtt/p/6946498.htm ...

  6. 【Python】数值运算函数

  7. JarvisOJ - Writeup(5.31更新)

    此篇用来记录我在jarivsOJ上的一些题解,只给解题思路,不放flag Misc 0x01 You Need Python(300) 题目有两个文件,一个py文件,另一个是经过编码的key 文件ke ...

  8. net core,redis的安装和试用

    一.window上面安装reids 在github上面下载,地址 安装完成后,进入安装目录打开redis服务, 这里双击Redis服务器即可启动Redis. 二.安装redis可视化工具 命令行操作r ...

  9. Python偶斐波那契数

    斐波那契数列中的每一项都是前两项的和.由1和2开始生成的斐波那契数列前10项为 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, … 考虑该斐波那契数列中不超过四百万的项,求其中为 ...

  10. Linux06——安装JDK、Tomcat、Eclipse

    一.安装JDK(具体解压命令在Linux02中) ①将JDK解压到opt目录下(opt就是文件夹) ②配置环境变量  vim  /etc/profile JAVA_HOME=/opt/jdk1.8.0 ...