Python中的format方法

Python中的format方法

内置的 format() 函数和 str.format() 方法把各个类型的格式化方式委托给相应的 .__format__(format_spec) 方法。format_spec是格式说明符,它是:

  • format(my_obj, 格式说明符) 的第二个参数,或者
  • str.format() 方法的格式字符串,{} 里代换字段中冒号后面的部分

格式说明符可以用在format函数中,或者用在str.format方法中。并且在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就是格式说明符。

在《流畅度Python》中,作者这样建议:

如果你对format()str.format()都感到陌生,根据我的教学经验,最好先学format()函数,因为它只使用格式规范微语言。学会这些表示法之后,再阅读格式字符串句法(“Format String Syntax”),学习str.format()方法使用的{:}代换字段表示法(包含转换标志!s!r!a)。

格式化微语言

格式说明符使用的表示法叫格式规范微语言。

format_spec字段包含值应如何呈现的规格描述,例如字段宽度、对齐、填充、小数精度等细节信息。 每种值类型可以定义自己的“格式化迷你语言”或对format_spec的解读方式。我的理解格式化微语言就像是对字符串进行打扮一样。

格式化微语言的语法如下:

format_spec     ::=  [[fill]align][sign][#][0][width][grouping_option][.precision][type]
fill            ::=  <any character>
align           ::=  "<" | ">" | "=" | "^"
sign            ::=  "+" | "-" | " "
width           ::=  digit+
grouping_option ::=  "_" | ","
precision       ::=  digit+
type            ::=  "b" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" | "n" | "o" | "s" | "x" | "X" | "%"

举例如下:

align字段+fill字段

如果指定了一个有效的align值,则可以在该值前面加一个fill字符,它可以为任意字符,如果省略则默认为空格符。

'{:<30}'.format('left aligned')
'left aligned                  '
'{:>30}'.format('right aligned')
'                 right aligned'
'{:^30}'.format('centered')
'           centered           '
'{:*^30}'.format('centered')  # use '*' as a fill char
'***********centered***********'

sign 字段:

该选项仅对数字类型有效,可以是以下之一:‘+’、‘-’、‘space’

>>> '{:+f}; {:+f}'.format(3.14, -3.14)  # show it always
'+3.140000; -3.140000'
>>> '{: f}; {: f}'.format(3.14, -3.14)  # show a space for positive numbers
' 3.140000; -3.140000'
>>> '{:-f}; {:-f}'.format(3.14, -3.14)  # show only the minus -- same as '{:f}; {:f}'
'3.140000; -3.140000'

'#'选项:

对于整数类型,当使用二进制、八进制或十六进制输出时,此选项会为输出值分别添加相应的'0b','0o','0x''0X'前缀。

整数实例:

>>> # format also supports binary numbers
>>> "int: {0:d};  hex: {0:x};  oct: {0:o};  bin: {0:b}".format(42)
'int: 42;  hex: 2a;  oct: 52;  bin: 101010'
>>> # with 0x, 0o, or 0b as prefix:
>>> "int: {0:d};  hex: {0:#x};  oct: {0:#o};  bin: {0:#b}".format(42)
'int: 42;  hex: 0x2a;  oct: 0o52;  bin: 0b101010'

这是当 '0' 紧接在字段宽度之前时的默认选项。

grouping_option字段:

'_'选项表示对浮点表示类型和整数表示类型'd'使用下划线作为千位分隔符。 对于整数表示类型'b','o','x''X',将为每 4 个数位插入一个下划线。 对于其他表示类型指定此选项则将导致错误。

','选项表示使用逗号作为千位分隔符。

'{:,}'.format(1234567890)
'1,234,567,890'
'{:_}'.format(1234567890)
'1_234_567_890'

width字段与0字段:

width是一个定义最小总字段宽度的十进制整数,包括任何前缀、分隔符和其他格式化字符。 如果未指定,则字段宽度将由内容确定。

当未显式给出对齐方式时,在width字段前加一个零 ('0') 字段将为数字类型启用感知正负号的零填充。 这相当于设置fill字符为'0'alignment类型为'='

'{:030}'.format(1234567890)
'000000000000000000001234567890'
'{:030}'.format(-1234567890)
'-00000000000000000001234567890'

precision字段:

precision是一个十进制数字,表示对于以'f'and'F'格式化的浮点数值要在小数点后显示多少个数位,或者对于以'g''G'格式化的浮点数值要在小数点前后共显示多少个数位。 对于非数字类型,该字段表示最大字段大小 —— 换句话说就是要使用多少个来自字段内容的字符。 对于整数值则不允许使用precision

'{:.2f}'.format(3.1415926)
'3.14'
'{:.2g}'.format(3.1415926)
'3.1'
'{:.2}'.format('xiao tong xue')
'xi'

type字段:

格式字符串句法

替换字段句法:

replacement_field ::=  "{" [field_name] ["!" conversion] [":" format_spec] "}"
field_name        ::=  arg_name ("." attribute_name | "[" element_index "]")*
arg_name          ::=  [identifier | digit+]
attribute_name    ::=  identifier
element_index     ::=  digit+ | index_string
index_string      ::=  <any source character except "]"> +
conversion        ::=  "r" | "s" | "a"
format_spec       ::=  <described in the next section>

其中关于format_spec字段已经在上文介绍完毕,下面介绍前面两个字段:

field_name字段:

field_name本身以一个数字或关键字arg_name打头。
如果为数字,则它指向一个位置参数,而如果为关键字,则它指向一个命名关键字参数。 如果格式字符串中的数字 arg_names 为 0, 1, 2, ... 的序列,它们可以全部省略(而非部分省略),数字 0, 1, 2, ... 将会按顺序自动插入。
由于arg_name不使用引号分隔,因此无法在格式字符串中指定任意的字典键 (例如字符串'10'':-]')。
arg_name之后可以带上任意数量的索引或属性表达式。'.name'形式的表达式会使用getattr()选择命名属性,而'[index]'形式的表达式会使用__getitem__()执行索引查找。

实例:

"First, thou shalt count to {0}"  # References first positional argument
"Bring me a {}"                   # Implicitly references the first positional argument
"From {} to {}"                   # Same as "From {0} to {1}"
"My quest is {name}"              # References keyword argument 'name'
"Weight in tons {0.weight}"       # 'weight' attribute of first positional arg
"Units destroyed: {players[0]}"   # First element of keyword argument 'players'.

按名称访问参数:

>>> 'Coordinates: {latitude}, {longitude}'.format(latitude='37.24N', longitude='-115.81W')
'Coordinates: 37.24N, -115.81W'
>>> coord = {'latitude': '37.24N', 'longitude': '-115.81W'}
>>> 'Coordinates: {latitude}, {longitude}'.format(**coord)
'Coordinates: 37.24N, -115.81W'

访问参数的属性:

>>> c = 3-5j
>>> ('The complex number {0} is formed from the real part {0.real} '
...  'and the imaginary part {0.imag}.').format(c)
'The complex number (3-5j) is formed from the real part 3.0 and the imaginary part -5.0.'
>>> class Point:
...     def __init__(self, x, y):
...         self.x, self.y = x, y
...     def __str__(self):
...         return 'Point({self.x}, {self.y})'.format(self=self)
...
>>> str(Point(4, 2))
'Point(4, 2)'

访问参数的项:

>>> coord = (3, 5)
>>> 'X: {0[0]};  Y: {0[1]}'.format(coord)
'X: 3;  Y: 5'

conversion字段:

使用 conversion 字段在格式化之前进行类型强制转换。 通常,格式化值的工作由值本身的 __format__() 方法来完成。 但是,在某些情况下最好强制将类型格式化为一个字符串,覆盖其本身的格式化定义。 通过在调用 __format__() 之前将值转换为字符串,可以绕过正常的格式化逻辑。
目前支持的转换旗标有三种: '!s' 会对值调用 str()'!r' 调用 repr()'!a' 则调用 ascii()
"Harold's a clever {0!s}"        # Calls str() on the argument first
"Bring out the holy {name!r}"    # Calls repr() on the argument first
"More {!a}"                      # Calls ascii() on the argument first


>>> "repr() shows quotes: {!r}; str() doesn't: {!s}".format('test1', 'test2')
"repr() shows quotes: 'test1'; str() doesn't: test2"

实现自定义的格式化微语言

假设存在一个类Vector2d,它的类定义如下:

from array import array
import math


class Vector2d:
    typecode = 'd'

    def __init__(self, x, y):
        self.__x = float(x)
        self.__y = float(y)

    @property
    def x(self):
        return self.__x

    @property
    def y(self):
        return self.__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 __hash__(self):
        return hash(self.x) ^ hash(self.y)

    def __abs__(self):
        return math.hypot(self.x, self.y)

    def __bool__(self):
        return bool(abs(self))

现在需要自定义他的格式化微语言,需求如下:

>>> 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_方法,代码如下:

    def __format__(self, fmt_spec=''):
        components = (format(c, fmt_spec) for c in self)
        return '({}, {})'.format(*components)

❶ 使用内置的 format 函数把 fmt_spec 应用到向量的各个分量上,构建一个可迭代的格式化字符串。

❷ 把格式化字符串代入公式 '(x, y)' 中。

但是以上只是将常规的format方法应用到每个元素上,如果需要自定义格式化微语言,实现以下的功能:

即,如果格式说明符以'p'结尾,那么在极坐标中显示向量,即<r, θ >,其中r是模,θ(西塔)是弧度;其他部分('p'之前的部分)像往常那样解释。

>>> 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>'

需要使用到atan2函数:

atan2 方法返回一个 -pi 到 pi 之间的数值,表示点 (x, y) 对应的偏移角度。这是一个逆时针角度,以弧度为单位,正X轴和点 (x, y) 与原点连线之间。函数接受的参数:先传递 y 坐标,然后是 x 坐标。

定义一个计算弧度的函数:

    def angle(self):
        return math.atan2(self.y, self.x)

完成Vector2d类定义:

# -*- coding:utf-8 -*-
"""
作者:tjygg
日期:2021年月18日
"""
from array import array
import math


class Vector2d:
    typecode = 'd'

    def __init__(self, x, y):
        self.__x = float(x)
        self.__y = float(y)

    @property
    def x(self):
        return self.__x

    @property
    def y(self):
        return self.__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 __hash__(self):
        return hash(self.x) ^ hash(self.y)

    def __abs__(self):
        return math.hypot(self.x, self.y)

    def __bool__(self):
        return bool(abs(self))

    def angle(self):
        return math.atan2(self.y, self.x)

    def __format__(self, fmt_spec=''):
        if fmt_spec.endswith('p'): #1
            fmt_spec = fmt_spec[:-1] #2
            coords = (self.__abs__(), self.angle()) #3
            outer_fmt = '<{}, {}>' #4
        else:
            coords = self %5
            outer_fmt = '({}, {})' %6
        component = (format(c, fmt_spec) for c in coords) #7
        return outer_fmt.format(*component) #8


print(format(Vector2d(1, 1), 'p'))
print(format(Vector2d(1, 1), '.3ep'))
print(format(Vector2d(1, 1), '0.5fp'))

❶ 如果格式代码以 'p' 结尾,使用极坐标。

❷ 从 fmt_spec 中删除 'p' 后缀。

❸ 构建一个元组,表示极坐标:(magnitude, angle)

❹ 把外层格式设为一对尖括号。

❺ 如果不以 'p' 结尾,使用 selfxy 分量构建直角坐标。

❻ 把外层格式设为一对圆括号。

❼ 使用各个分量生成可迭代的对象,构成格式化字符串。

❽ 把格式化字符串代入外层格式。

发布于 2021-10-24 12:34