- 前言
- 目标读者
- 非目标读者
- 本书的结构
- 以实践为基础
- 硬件
- 杂谈:个人的一点看法
- 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 术语表
- 作者简介
- 关于封面
9.2 再谈向量类
为了说明用于生成对象表示形式的众多方法,我们将使用一个 Vector2d 类,它与第 1 章中的类似。这一节和接下来的几节会不断实现这个类。我们期望 Vector2d 实例具有的基本行为如示例 9-1 所示。
示例 9-1 Vector2d 实例有多种表示形式
>>> v1 = Vector2d(3, 4) >>> print(v1.x, v1.y) ➊ 3.0 4.0 >>> x, y = v1 ➋ >>> x, y (3.0, 4.0) >>> v1 ➌ Vector2d(3.0, 4.0) >>> v1_clone = eval(repr(v1)) ➍ >>> v1 == v1_clone ➎ True >>> print(v1) ➏ (3.0, 4.0) >>> octets = bytes(v1) ➐ >>> octets b'd\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' >>> abs(v1) ➑ 5.0 >>> bool(v1), bool(Vector2d(0, 0)) ➒
❶ Vector2d 实例的分量可以直接通过属性访问(无需调用读值方法)。
❷ Vector2d 实例可以拆包成变量元组。
❸ repr 函数调用 Vector2d 实例,得到的结果类似于构建实例的源码。
❹ 这里使用 eval 函数,表明 repr 函数调用 Vector2d 实例得到的是对构造方法的准确表述。2
2这里使用 eval 函数克隆对象是为了说明 repr 方法。使用 copy.copy 函数克隆实例更安全也更快速。
❺ Vector2d 实例支持使用 == 比较;这样便于测试。
❻ print 函数会调用 str 函数,对 Vector2d 来说,输出的是一个有序对。
❼ bytes 函数会调用 __bytes__ 方法,生成实例的二进制表示形式。
❽ abs 函数会调用 __abs__ 方法,返回 Vector2d 实例的模。
❾ bool 函数会调用 __bool__ 方法,如果 Vector2d 实例的模为零,返回 False,否则返回 True。
示例 9-1 中的 Vector2d 类在 vector2d_v0.py 文件中实现(见示例 9-2)。这段代码基于示例 1-2,除了 == 之外(在测试中用得到),其他中缀运算符将在第 13 章实现。现在,Vector2d 用到了几个特殊方法,这些方法提供的操作是 Python 高手期待设计良好的对象所提供的。
示例 9-2 vector2d_v0.py:目前定义的都是特殊方法
from array import array import math class Vector2d: typecode = 'd' ➊ def __init__(self, x, y): self.x = float(x) ➋ self.y = float(y) def __iter__(self): return (i for i in (self.x, self.y)) ➌ def __repr__(self): class_name = type(self).__name__ return '{}({!r}, {!r})'.format(class_name, *self) ➍ def __str__(self): return str(tuple(self)) ➎ def __bytes__(self): return (bytes([ord(self.typecode)]) + ➏ bytes(array(self.typecode, self))) ➐ def __eq__(self, other): return tuple(self) == tuple(other) ➑ def __abs__(self): return math.hypot(self.x, self.y) ➒ def __bool__(self): return bool(abs(self)) ➓
❶ typecode 是类属性,在 Vector2d 实例和字节序列之间转换时使用。
❷ 在 __init__ 方法中把 x 和 y 转换成浮点数,尽早捕获错误,以防调用 Vector2d 函数时传入不当参数。
❸ 定义 __iter__ 方法,把 Vector2d 实例变成可迭代的对象,这样才能拆包(例如,x, y = my_vector)。这个方法的实现方式很简单,直接调用生成器表达式一个接一个产出分量。3
3这一行也可以写成 yield self.x; yield.self.y。第 14 章会进一步讨论 __iter__ 特殊方法、生成器表达式和 yield 关键字。
❹ __repr__ 方法使用 {!r} 获取各个分量的表示形式,然后插值,构成一个字符串;因为 Vector2d 实例是可迭代的对象,所以 *self 会把 x 和 y 分量提供给 format 函数。
❺ 从可迭代的 Vector2d 实例中可以轻松地得到一个元组,显示为一个有序对。
❻ 为了生成字节序列,我们把 typecode 转换成字节序列,然后……
❼ ……迭代 Vector2d 实例,得到一个数组,再把数组转换成字节序列。
❽ 为了快速比较所有分量,在操作数中构建元组。对 Vector2d 实例来说,可以这样做,不过仍有问题。参见下面的警告。
❾ 模是 x 和 y 分量构成的直角三角形的斜边长。
❿ __bool__ 方法使用 abs(self) 计算模,然后把结果转换成布尔值,因此,0.0 是 False,非零值是 True。
示例 9-2 中的 __eq__ 方法,在两个操作数都是 Vector2d 实例时可用,不过拿 Vector2d 实例与其他具有相同数值的可迭代对象相比,结果也是 True(如 Vector(3, 4) == [3, 4])。这个行为可以视作特性,也可以视作缺陷。第 13 章讲到运算符重载时才能进一步讨论。
我们已经定义了很多基本方法,但是显然少了一个操作:使用 bytes() 函数生成的二进制表示形式重建 Vector2d 实例。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论