Chapter 10 Sequence Hacking, Hashing, and Slicing
把协议当作正式接口。
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)(没有c时c=None);- 有逗号时接收到的实参为元组,可能包含多个切片对象与整数(在基本类型中不支持);
- 切片对象的
.indices(len)方法会根据序列的长度len进行“整顿”,截掉超出边界的索引,补充没有给定的切片参数,返回落在边界范围内的索引元组。
10.5 第三版:动态存储属性
- 在属性查找失败后(查找顺序:实例、类、父类……),会调用
.__getattr__方法,传入self和属性名称的字符串形式; - 由于 Python 支持实例属性的运行时创建,因此类似
v.x = 10的赋值是不会调用.__getattr__方法的,而是会创建一个属性。这可能导致错误; - 在属性赋值时,会调用
.__setattr__方法,传入self和属性对应的name、value; - 需要注意:支持多重继承时,必须要使用
super().method()提供默认的标准行为; - 多数时候,
__getattr__方法和__setattr__方法需要同时定义,防止行为不一致。
10.6 第四版:散列和快速等值测试
- 散列函数:使用
map、functools.reduce、operator.xor求取各元素散列值的异或; reduce函数最好提供三个参数function,iterable和initializer(初始值);- 可以使用
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函数可用于合并迭代不同的可迭代对象,合并成为一个生成器表达式。