- 前言
- 目标读者
- 非目标读者
- 本书的结构
- 以实践为基础
- 硬件
- 杂谈:个人的一点看法
- 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.5 格式化显示
内置的 format() 函数和 str.format() 方法把各个类型的格式化方式委托给相应的 .__format__(format_spec) 方法。format_spec 是格式说明符,它是:
format(my_obj, format_spec) 的第二个参数,或者
str.format() 方法的格式字符串,{} 里代换字段中冒号后面的部分
例如:
>>> brl = 1/2.43 # BRL到USD的货币兑换比价 >>> brl 0.4115226337448559 >>> format(brl, '0.4f') # ➊ '0.4115' >>> '1 BRL = {rate:0.2f} USD'.format(rate=brl) # ➋ '1 BRL = 0.41 USD'
❶ 格式说明符是 '0.4f'。
❷ 格式说明符是 '0.2f'。代换字段中的 'rate' 子串是字段名称,与格式说明符无关,但是它决定把 .format() 的哪个参数传给代换字段。
第 2 条标注指出了一个重要知识点:'{0.mass:5.3e}' 这样的格式字符串其实包含两部分,冒号左边的 '0.mass' 在代换字段句法中是字段名,冒号后面的 '5.3e' 是格式说明符。格式说明符使用的表示法叫格式规范微语言(“Format Specification Mini-Language”)。
如果你对 format() 和 str.format() 都感到陌生,根据我的教学经验,最好先学 format() 函数,因为它只使用格式规范微语言。学会这些表示法之后,再阅读格式字符串句法(“Format String Syntax”),学习 str.format() 方法使用的 {:} 代换字段表示法(包含转换标志 !s、!r 和 !a)。
格式规范微语言为一些内置类型提供了专用的表示代码。比如,b 和 x 分别表示二进制和十六进制的 int 类型,f 表示小数形式的 float 类型,而 % 表示百分数形式:
>>> format(42, 'b') '101010' >>> format(2/3, '.1%') '66.7%'
格式规范微语言是可扩展的,因为各个类可以自行决定如何解释 format_spec 参数。例如, datetime 模块中的类,它们的 __format__ 方法使用的格式代码与 strftime() 函数一样。下面是内置的 format() 函数和 str.format() 方法的几个示例:
>>> from datetime import datetime >>> now = datetime.now() >>> format(now, '%H:%M:%S') '18:49:05' >>> "It's now {:%I:%M %p}".format(now) "It's now 06:49 PM"
如果类没有定义 __format__ 方法,从 object 继承的方法会返回 str(my_object)。我们为 Vector2d 类定义了 __str__ 方法,因此可以这样做:
>>> v1 = Vector2d(3, 4) >>> format(v1) '(3.0, 4.0)'
然而,如果传入格式说明符,object.__format__ 方法会抛出 TypeError:
>>> format(v1, '.3f') Traceback (most recent call last): ... TypeError: non-empty format string passed to object.__format__
我们将实现自己的微语言来解决这个问题。首先,假设用户提供的格式说明符是用于格式化向量中各个浮点数分量的。我们想达到的效果是:
>>> v1 = Vector2d(3, 4) >>> format(v1) '(3.0, 4.0)' >>> format(v1, '.2f') '(3.00, 4.00)' >>> format(v1, '.3e') '(3.000e+00, 4.000e+00)'
实现这种输出的 __format__ 方法如示例 9-5 所示。
示例 9-5 Vector2d.__format__ 方法,第 1 版
# 在Vector2d类中定义 def __format__(self, fmt_spec=''): components = (format(c, fmt_spec) for c in self) # ➊ return '({}, {})'.format(*components) # ➋
❶ 使用内置的 format 函数把 fmt_spec 应用到向量的各个分量上,构建一个可迭代的格式化字符串。
❷ 把格式化字符串代入公式 '(x, y)' 中。
下面要在微语言中添加一个自定义的格式代码:如果格式说明符以 'p' 结尾,那么在极坐标中显示向量,即 <r, θ >,其中 r 是模,θ(西塔)是弧度;其他部分('p' 之前的部分)像往常那样解释。
为自定义的格式代码选择字母时,我会避免使用其他类型用过的字母。在格式规范微语言中我们看到,整数使用的代码有 'bcdoxXn',浮点数使用的代码有 'eEfFgGn%',字符串使用的代码有 's'。因此,我为极坐标选的代码是 'p'。各个类使用自己的方式解释格式代码,在自定义的格式代码中重复使用代码字母不会出错,但是可能会让用户困惑。
对极坐标来说,我们已经定义了计算模的 __abs__ 方法,因此还要定义一个简单的 angle 方法,使用 math.atan2() 函数计算角度。angle 方法的代码如下:
# 在Vector2d类中定义 def angle(self): return math.atan2(self.y, self.x)
这样便可以增强 __format__ 方法,计算极坐标,如示例 9-6 所示。
示例 9-6 Vector2d.__format__ 方法,第 2 版,现在能计算极坐标了
def __format__(self, fmt_spec=''): if fmt_spec.endswith('p'): ➊ fmt_spec = fmt_spec[:-1] ➋ coords = (abs(self), self.angle()) ➌ outer_fmt = '<{}, {}>' ➍ else: coords = self ➎ outer_fmt = '({}, {})' ➏ components = (format(c, fmt_spec) for c in coords) ➐ return outer_fmt.format(*components) ➑
❶ 如果格式代码以 'p' 结尾,使用极坐标。
❷ 从 fmt_spec 中删除 'p' 后缀。
❸ 构建一个元组,表示极坐标:(magnitude, angle)。
❹ 把外层格式设为一对尖括号。
❺ 如果不以 'p' 结尾,使用 self 的 x 和 y 分量构建直角坐标。
❻ 把外层格式设为一对圆括号。
❼ 使用各个分量生成可迭代的对象,构成格式化字符串。
❽ 把格式化字符串代入外层格式。
示例 9-6 中的代码得到的结果如下:
>>> format(Vector2d(1, 1), 'p') '<1.4142135623730951, 0.7853981633974483>' >>> format(Vector2d(1, 1), '.3ep') '<1.414e+00, 7.854e-01>' >>> format(Vector2d(1, 1), '0.5fp') '<1.41421, 0.78540>'
如本节所示,为用户自定义的类型扩展格式规范微语言并不难。
下面换个话题,它不仅事关对象的外观:我们将把 Vector2d 变成可散列的,这样便可以构建向量集合,或者把向量当作 dict 的键使用。不过在此之前,必须让向量不可变。详情参见下一节。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论