- 前言
- 目标读者
- 非目标读者
- 本书的结构
- 以实践为基础
- 硬件
- 杂谈:个人的一点看法
- 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.7 Vector 类第5版:格式化
Vector 类的 __format__ 方法与 Vector2d 类的相似,但是不使用极坐标,而使用球面坐标(也叫超球面坐标),因为 Vector 类支持 n 个维度,而超过四维后,球体变成了“超球体”。8 因此,我们会把自定义的格式后缀由 'p' 变成 'h'。
8Wolfram Mathworld 网站中有一篇介绍超球体的文章;维基百科会把“超球体”词条重定向到“n 维球体”词条。
9.5 节说过,扩展格式规范微语言时,最好避免重用内置类型支持的格式代码。这里对微语言的扩展还会用到浮点数的格式代码 'eEfFgGn%',而且保持原意,因此绝对要避免重用代码。整数使用的格式代码有 'bcdoxXn',字符串使用的是 's'。在 Vector2d 类中,我选择使用 'p' 表示极坐标。使用 'h' 表示超球面坐标(hyperspherical coordinate)是个不错的选择。
例如,对四维空间(len(v) == 4)中的 Vector 对象来说,'h' 代码得到的结果是这样:<r, Φ1, Φ2, Φ3>。其中,r 是模(abs(v)),余下三个数是角坐标 Φ1、Φ2 和 Φ3。 下面几个示例摘自 vector_v5.py 的 doctest(参见示例 10-16),是四维球面坐标格式:
>>> format(Vector([-1, -1, -1, -1]), 'h') '<2.0, 2.0943951023931957, 2.186276035465284, 3.9269908169872414>' >>> format(Vector([2, 2, 2, 2]), '.3eh') '<4.000e+00, 1.047e+00, 9.553e-01, 7.854e-01>' >>> format(Vector([0, 1, 0, 0]), '0.5fh') '<1.00000, 1.57080, 0.00000, 0.00000>'
在小幅改动 __format__ 方法之前,我们要定义两个辅助方法:一个是 angle(n),用于计算某个角坐标(如 Φ1);另一个是 angles(),返回由所有角坐标构成的可迭代对象。我们不会讲解其中涉及的数学原理,如果你好奇的话,可以查看维基百科中的“n 维球体”词条,那里有几个公式,我就是使用它们把 Vector 实例分量数组内的笛卡儿坐标转换成球面坐标的。
示例 10-16 是 vector_v5.py 脚本的完整代码,包含自 10.2 节以来实现的所有代码和本节实现的自定义格式。
示例 10-16 vector_v5.py:Vector 类最终版的 doctest 和全部代码;带标号的那几行是为了支持 __format__ 方法而添加的代码
""" A multidimensional ``Vector`` class, take 5 A ``Vector`` is built from an iterable of numbers:: >>> 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, ...]) Tests with two dimensions (same results as ``vector2d_v1.py``):: >>> v1 = Vector([3, 4]) >>> x, y = v1 >>> x, y (3.0, 4.0) >>> v1 Vector([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(Vector([0, 0])) (True, False) Test of ``.frombytes()`` class method: >>> v1_clone = Vector.frombytes(bytes(v1)) >>> v1_clone Vector([3.0, 4.0]) >>> v1 == v1_clone True Tests with three dimensions:: >>> v1 = Vector([3, 4, 5]) >>> x, y, z = v1 >>> x, y, z (3.0, 4.0, 5.0) >>> v1 Vector([3.0, 4.0, 5.0]) >>> v1_clone = eval(repr(v1)) >>> v1 == v1_clone True >>> print(v1) (3.0, 4.0, 5.0) >>> abs(v1) # doctest:+ELLIPSIS 7.071067811... >>> bool(v1), bool(Vector([0, 0, 0])) (True, False) Tests with many dimensions:: >>> v7 = Vector(range(7)) >>> v7 Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) >>> abs(v7) # doctest:+ELLIPSIS 9.53939201... Test of ``.__bytes__`` and ``.frombytes()`` methods:: >>> v1 = Vector([3, 4, 5]) >>> v1_clone = Vector.frombytes(bytes(v1)) >>> v1_clone Vector([3.0, 4.0, 5.0]) >>> v1 == v1_clone True Tests of sequence behavior:: >>> v1 = Vector([3, 4, 5]) >>> len(v1) 3 >>> v1[0], v1[len(v1)-1], v1[-1] (3.0, 5.0, 5.0) Test of slicing:: >>> v7 = Vector(range(7)) >>> v7[-1] 6.0 >>> v7[1:4] Vector([1.0, 2.0, 3.0]) >>> v7[-1:] Vector([6.0]) >>> v7[1,2] Traceback (most recent call last): ... TypeError: Vector indices must be integers Tests of dynamic attribute access:: >>> v7 = Vector(range(10)) >>> v7.x 0.0 >>> v7.y, v7.z, v7.t (1.0, 2.0, 3.0) Dynamic attribute lookup failures:: >>> v7.k Traceback (most recent call last): ... AttributeError: 'Vector' object has no attribute 'k' >>> v3 = Vector(range(3)) >>> v3.t Traceback (most recent call last): ... AttributeError: 'Vector' object has no attribute 't' >>> v3.spam Traceback (most recent call last): ... AttributeError: 'Vector' object has no attribute 'spam' Tests of hashing:: >>> v1 = Vector([3, 4]) >>> v2 = Vector([3.1, 4.2]) >>> v3 = Vector([3, 4, 5]) >>> v6 = Vector(range(6)) >>> hash(v1), hash(v3), hash(v6) (7, 2, 1) Most hash values of non-integers vary from a 32-bit to 64-bit CPython build:: >>> import sys >>> hash(v2) == (384307168202284039 if sys.maxsize > 2**32 else 357915986) True Tests of ``format()`` with Cartesian coordinates in 2D:: >>> v1 = Vector([3, 4]) >>> format(v1) '(3.0, 4.0)' >>> format(v1, '.2f') '(3.00, 4.00)' >>> format(v1, '.3e') '(3.000e+00, 4.000e+00)' Tests of ``format()`` with Cartesian coordinates in 3D and 7D:: >>> v3 = Vector([3, 4, 5]) >>> format(v3) '(3.0, 4.0, 5.0)' >>> format(Vector(range(7))) '(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0)' Tests of ``format()`` with spherical coordinates in 2D, 3D and 4D:: >>> format(Vector([1, 1]), 'h') # doctest:+ELLIPSIS '<1.414213..., 0.785398...>' >>> format(Vector([1, 1]), '.3eh') '<1.414e+00, 7.854e-01>' >>> format(Vector([1, 1]), '0.5fh') '<1.41421, 0.78540>' >>> format(Vector([1, 1, 1]), 'h') # doctest:+ELLIPSIS '<1.73205..., 0.95531..., 0.78539...>' >>> format(Vector([2, 2, 2]), '.3eh') '<3.464e+00, 9.553e-01, 7.854e-01>' >>> format(Vector([0, 0, 0]), '0.5fh') '<0.00000, 0.00000, 0.00000>' >>> format(Vector([-1, -1, -1, -1]), 'h') # doctest:+ELLIPSIS '<2.0, 2.09439..., 2.18627..., 3.92699...>' >>> format(Vector([2, 2, 2, 2]), '.3eh') '<4.000e+00, 1.047e+00, 9.553e-01, 7.854e-01>' >>> format(Vector([0, 1, 0, 0]), '0.5fh') '<1.00000, 1.57080, 0.00000, 0.00000>' """ from array import array import reprlib import math import numbers import functools import operator import itertools ➊ 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 (len(self) == len(other) and all(a == b for a, b in zip(self, other))) def __hash__(self): hashes = (hash(x) for x in self) return functools.reduce(operator.xor, hashes, 0) def __abs__(self): return math.sqrt(sum(x * x for x in self)) def __bool__(self): return bool(abs(self)) def __len__(self): return len(self._components) def __getitem__(self, index): cls = type(self) if isinstance(index, slice): return cls(self._components[index]) elif isinstance(index, numbers.Integral): return self._components[index] else: msg = '{.__name__} indices must be integers' raise TypeError(msg.format(cls)) shortcut_names = 'xyzt' def __getattr__(self, name): cls = type(self) if len(name) == 1: pos = cls.shortcut_names.find(name) if 0 <= pos < len(self._components): return self._components[pos] msg = '{.__name__!r} object has no attribute {!r}' raise AttributeError(msg.format(cls, name)) def angle(self, n): ➋ r = math.sqrt(sum(x * x for x in self[n:])) a = math.atan2(r, self[n-1]) if (n == len(self) - 1) and (self[-1] < 0): return math.pi * 2 - a else: return a def angles(self): ➌ return (self.angle(n) for n in range(1, len(self))) def __format__(self, fmt_spec=''): if fmt_spec.endswith('h'): # 超球面坐标 fmt_spec = fmt_spec[:-1] coords = itertools.chain([abs(self)], self.angles()) ➍ outer_fmt = '<{}>' ➎ else: coords = self outer_fmt = '({})' ➏ components = (format(c, fmt_spec) for c in coords) ➐ return outer_fmt.format(', '.join(components)) ➑ @classmethod def frombytes(cls, octets): typecode = chr(octets[0]) memv = memoryview(octets[1:]).cast(typecode) return cls(memv)
❶ 为了在 __format__ 方法中使用 chain 函数,导入 itertools 模块。
❸ 创建生成器表达式,按需计算所有角坐标。
❹ 使用 itertools.chain 函数生成生成器表达式,无缝迭代向量的模和各个角坐标。
❺ 配置使用尖括号显示球面坐标。
❻ 配置使用圆括号显示笛卡儿坐标。
❼ 创建生成器表达式,按需格式化各个坐标元素。
❽ 把以逗号分隔的格式化分量插入尖括号或圆括号。
我们在 __format__、angle 和 angles 中大量使用了生成器表达式,不过我们的目的是让 Vector 类的 __format__ 方法与 Vector2d 类处在同一水平上。第 14 章讨论生成器时会使用 Vector 类中的部分代码举例,然后详细说明生成器的技巧。
本章的任务到此结束。第 13 章会改进 Vector 类,让它支持中缀运算符。本章的目的是探讨如何编写集合类广泛使用的几个特殊方法。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论