把协议当作正式接口。

10.1 Vector 类:用户定义的序列类型

10.2 第一版:与 Vector2d 类兼容

  • 序列类型的构造方法最好接受可迭代对象为参数;
  • 可以使用 reprlib 模块生成长度有限的调试用字符串,适用于 __repr__ 方法:reprlib.repr(obj) 函数能够返回 obj 对象的有限长度表现形式(与控制台输出等价)。

10.3 协议和鸭子类型

  • 协议是非正式的接口,只在文档中定义,在代码中不定义。协议不是具体的方法调用,而是一套所应支持方法的规定。如序列协议需要实现 __len____getitem__
  • 鸭子类型:不要求是某一类型的子类,只要它实现了类型的协议,它就有符合对应类型的行为。

10.4 第二版:可切片的序列

  • 使用切片操作时,__getitem__ 方法接受到的是方括号内的内容:
    • a: b: c 会变成 slice(a,b,c)(没有 cc=None);
    • 有逗号时接收到的实参为元组,可能包含多个切片对象与整数(在基本类型中不支持);
  • 切片对象的 .indices(len) 方法会根据序列的长度 len 进行“整顿”,截掉超出边界的索引,补充没有给定的切片参数,返回落在边界范围内的索引元组。

10.5 第三版:动态存储属性

  • 在属性查找失败后(查找顺序:实例、类、父类……),会调用 .__getattr__ 方法,传入 self属性名称的字符串形式
  • 由于 Python 支持实例属性的运行时创建,因此类似 v.x = 10赋值是不会调用 .__getattr__ 方法的,而是会创建一个属性。这可能导致错误;
  • 在属性赋值时,会调用 .__setattr__ 方法,传入 self 和属性对应的 namevalue
  • 需要注意:支持多重继承时,必须要使用 super().method() 提供默认的标准行为
  • 多数时候,__getattr__ 方法和 __setattr__ 方法需要同时定义,防止行为不一致。

10.6 第四版:散列和快速等值测试

  • 散列函数:使用 mapfunctools.reduceoperator.xor 求取各元素散列值的异或
  • reduce 函数最好提供三个参数 functioniterableinitializer(初始值);
  • 可以使用 zip 函数实现更简洁的并行迭代(可用于比较),返回生成器。当一个可迭代对象耗尽后,不发出警告就停止,因此需要提前比较长度;
  • itertools.zip_longest 函数能够使用 fillvalue= 参数填充缺失的值;
  • 快速等值比较
return len(self) == len(other) and all(a == b for a, b in zip(self, other))

10.7 第五版:格式化

  • 对格式规范微语言进行扩展(自定义 format)时,需要注意避免内置类型支持的格式代码:浮点数 eEfFgGn%、整数 bndoxXn、字符串 s。使用这些格式代码时要保持原意
  • itertools.chain 函数可用于合并迭代不同的可迭代对象,合并成为一个生成器表达式。