第四部分第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. Python - isinstance()更深刻的理解

    起因经过 今天翻collections.abc的文档时,我知道list的实例在逻辑上(因为duck typing 鸭子类型)是Container和不能hash的(因为list可变),就试下面的代码是否 ...

  2. python面试的100题(2)

    def print_directory_contents(sPath): """ 这个函数接收文件夹的名称作为输入参数 返回该文件夹中文件的路径 以及其包含文件夹中文件的 ...

  3. 关于jquery ajax不执行success回调函数

    检查error函数是否执行,发现错误信息为parseerror,表示jquery解析返回结果时失败,只需要将ajax参数dataType:"json"改为"text js ...

  4. CAN报文格式

    CAN协议的报文传输过程中有:数据帧.远程帧.错误帧.过载帧和帧间隔. 1.数据帧:用于发送节点向接收节点传送数据的帧. 2.远程帧:用于接收节点向具有相同ID的发送节点传送数据的帧. 3.错误帧:用 ...

  5. 查看和清理相关yum安装应用--例如docker包

    查看和清理相关yum安装应用--例如docker包 待办 https://blog.csdn.net/CSDN_duomaomao/article/details/78997138

  6. Hadoop2.0之YARN组件

    官方文档:https://hadoop.apache.org/docs/stable/,目前官方已经是3.x,但yarn机制没有太大变化 一.简介 在Hadoop1.0中,没有yarn,所有的任务调度 ...

  7. 解决com.netflix.zuul.FilterProcessor : null异常

    ZuulFilter过滤器的filterType方法返回一个字符串代表过滤器的类型,如果return null就会出现以上错误. 在zuul中有以下四种不同生命周期的过滤器类型 pre:路由之前 ro ...

  8. .NET知识梳理——2.反射

    1. 反射 1.1        DLL-IL-Metadata-反射 DLL:程序集,包含IL 和Metadada IL:面向对象中间语言(不太好阅读) Metadata描述了dll.exe中的各种 ...

  9. 移动端 输入框 input 被弹出来的键盘 挡住

    给被挡住的input或者textarea加一个id,然后在click事件里调用下面的代码 document.querySelector('#xxx').scrollIntoView();

  10. opencv:霍夫圆检测

    #include <opencv2/opencv.hpp> #include <iostream> using namespace cv; using namespace st ...