- 前言
- 目标读者
- 非目标读者
- 本书的结构
- 以实践为基础
- 硬件
- 杂谈:个人的一点看法
- Python 术语表
- Python 版本表
- 排版约定
- 使用代码示例
- 第一部分 序幕
- 第 1 章 Python 数据模型
- 第二部分 数据结构
- 第 2 章 序列构成的数组
- 第 3 章 字典和集合
- 第 4 章 文本和字节序列
- 第三部分 把函数视作对象
- 第 5 章 一等函数
- 第 6 章 使用一等函数实现设计模式
- 第 7 章 函数装饰器和闭包
- 第四部分 面向对象惯用法
- 第 8 章 对象引用、可变性和垃圾回收
- 第 9 章 符合 Python 风格的对象
- 第 10 章 序列的修改、散列和切片
- 第 11 章 接口:从协议到抽象基类
- 第 12 章 继承的优缺点
- 第 13 章 正确重载运算符
- 第五部分 控制流程
- 第 14 章 可迭代的对象、迭代器和生成器
- 14.1 Sentence 类第1版:单词序列
- 14.2 可迭代的对象与迭代器的对比
- 14.3 Sentence 类第2版:典型的迭代器
- 14.4 Sentence 类第3版:生成器函数
- 14.5 Sentence 类第4版:惰性实现
- 14.6 Sentence 类第5版:生成器表达式
- 14.7 何时使用生成器表达式
- 14.8 另一个示例:等差数列生成器
- 14.9 标准库中的生成器函数
- 14.10 Python 3.3 中新出现的句法:yield from
- 14.11 可迭代的归约函数
- 14.12 深入分析 iter 函数
- 14.13 案例分析:在数据库转换工具中使用生成器
- 14.14 把生成器当成协程
- 14.15 本章小结
- 14.16 延伸阅读
- 第 15 章 上下文管理器和 else 块
- 第 16 章 协程
- 第 17 章 使用期物处理并发
- 第 18 章 使用 asyncio 包处理并发
- 第六部分 元编程
- 第 19 章 动态属性和特性
- 第 20 章 属性描述符
- 第 21 章 类元编程
- 结语
- 延伸阅读
- 附录 A 辅助脚本
- Python 术语表
- 作者简介
- 关于封面
10.2 Vector 类第1版:与 Vector2d 类兼容
Vector 类的第 1 版要尽量与前一章定义的 Vector2d 类兼容。
然而我们会故意不让 Vector 的构造方法与 Vector2d 的构造方法兼容。为了编写 Vector(3, 4) 和 Vector(3, 4, 5) 这样的代码,我们可以让 __init__ 方法接受任意个参数(通过 *args);但是,序列类型的构造方法最好接受可迭代的对象为参数,因为所有内置的序列类型都是这样做的。示例 10-1 展示了 Vector 类的几种实例化方式。
示例 10-1 测试 Vector.__init__ 和 Vector.__repr__ 方法
>>> Vector([3.1, 4.2]) Vector([3.1, 4.2]) >>> Vector((3, 4, 5)) Vector([3.0, 4.0, 5.0]) >>> Vector(range(10)) Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])
除了新构造方法的签名外,我还确保了传入两个分量(如 Vector([3, 4]))时,Vector2d 类(如 Vector2d(3, 4))的每个测试都能通过,而且得到相同的结果。
如果 Vector 实例的分量超过 6 个,repr() 生成的字符串就会使用 ... 省略一部分,如示例 10-1 中的最后一行所示。包含大量元素的集合类型一定要这么做,因为字符串表示形式是用于调试的(因此不想让大型对象在控制台或日志中输出几千行内容)。使用 reprlib 模块可以生成长度有限的表示形式,如示例 10-2 所示。
在 Python 2 中,reprlib 模块的名字是 repr。2to3 工具能自动重写 repr 导入的内容。
示例 10-2 是第 1 版 Vector 类的实现代码(以示例 9-2 和示例 9-3 中的代码为基础)。
示例 10-2 vector_v1.py:从 vector2d_v1.py 衍生而来
from array import array import reprlib import math class Vector: typecode = 'd' def __init__(self, components): self._components = array(self.typecode, components) ➊ def __iter__(self): return iter(self._components) ➋ def __repr__(self): components = reprlib.repr(self._components) ➌ components = components[components.find('['):-1] ➍ return 'Vector({})'.format(components) def __str__(self): return str(tuple(self)) def __bytes__(self): return (bytes([ord(self.typecode)]) + bytes(self._components)) ➎ def __eq__(self, other): return tuple(self) == tuple(other) def __abs__(self): return math.sqrt(sum(x * x for x in self)) ➏ def __bool__(self): return bool(abs(self)) @classmethod def frombytes(cls, octets): typecode = chr(octets[0]) memv = memoryview(octets[1:]).cast(typecode) return cls(memv) ➐
❶ self._components 是“受保护的”实例属性,把 Vector 的分量保存在一个数组中。
❷ 为了迭代,我们使用 self._components 构建一个迭代器。1
1iter() 函数和 __iter__ 方法在第 14 章讨论。
❸ 使用 reprlib.repr() 函数获取 self._components 的有限长度表示形式(如 array('d', [0.0, 1.0, 2.0, 3.0, 4.0, ...]))。
❹ 把字符串插入 Vector 的构造方法调用之前,去掉前面的 array('d' 和后面的 )。
❺ 直接使用 self._components 构建 bytes 对象。
❻ 不能使用 hypot 方法了,因此我们先计算各分量的平方之和,然后再使用 sqrt 方法开平方。
❼ 我们只需在 Vector2d.frombytes 方法的基础上改动最后一行:直接把 memoryview 传给构造方法,不用像前面那样使用 * 拆包。
我使用 reprlib.repr 的方式需要做些说明。这个函数用于生成大型结构或递归结构的安全表示形式,它会限制输出字符串的长度,用 '...' 表示截断的部分。我希望 Vector 实例的表示形式是 Vector([3.0, 4.0, 5.0]) 这样,而不是 Vector(array('d', [3.0, 4.0, 5.0])),因为 Vector 实例中的数组是实现细节。因为这两种构造方法的调用方式所构建的 Vector 对象是一样的,所以我选择使用更简单的句法,即传入列表参数。
编写 __repr__ 方法时,本可以使用这个表达式生成简化的 components 显示形式:reprlib.repr(list(self._components))。然而,这么做有点浪费,因为要把 self._components 中的每个元素复制到一个列表中,然后使用列表的表示形式。我没有这么做,而是直接把 self._components 传给 reprlib.repr 函数,然后去掉 [] 外面的字符,如示例 10-2 中 __repr__ 方法的第二行所示。
调用 repr() 函数的目的是调试,因此绝对不能抛出异常。如果 __repr__ 方法的实现有问题,那么必须处理,尽量输出有用的内容,让用户能够识别目标对象。
注意,__str__、__eq__ 和 __bool__ 方法与 Vector2d 类中的一样,而 frombytes 方法也只变了一个字符(最后一行把 * 去掉了)。这是 Vector2d 可迭代的好处之一。
顺便说一下,我们本可以让 Vector 继承 Vector2d,但是我没这么做,原因有二。其一,两个构造方法不兼容,因此不建议继承。这一点可以通过适当处理 __init__ 方法的参数解决,不过第二个原因更重要:我想把 Vector 类当作单独的示例,以此实现序列协议。接下来,我们先讨论协议这个术语,然后实现序列协议。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论