返回介绍

9.5 格式化显示

发布于 2024-02-05 21:59:47 字数 4676 浏览 0 评论 0 收藏 0

内置的 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文