Python3 字符串格式化

0

所谓格式化,指将现有的数据组成得到一个完整的字符串用于输出、传参等的方式。Python2中,通常使用百分号的方式来对字符串进行格式化操作。例如:

>>> 'I am %s, %d years old.' % ('Jack', 17)
'I am Jack, 17 years old.'

这种方式来源于C语言的 printf() 等所使用的转换说明,这么做不够灵活,格式化较为麻烦。Python3中更新了一种更为现代化的格式化方法,可以更方便地达到预期的输出效果。

Python3中的格式化

使用str.format()方法

Python3中格式化的基本思路是:在字符串中待格式数据用花括号对“ {} ”占位,在格式化时再用实际的内容替换。str.format() 方法告诉字符串中的花括号对用哪些数据替换。该方法格式化的一个基本示例为:

>>> 'I am {}, {} years old'.format('Jack', 17)
'I am Jack, 17 years old'

使用这种方式对字符串格式化更加强大。在这个基本的示例中,它也非常直观地表明了如何进行格式化。


使用 str.format() 格式化的第一个优点是,它能充分利用提供的数据。

例如,可以在花括号内设置一个整数,代表使用哪一个格式数据填充这部分内容:

>>> 'He is {0}. {0} is {1} years old.'.format('Jack', 17)
'He is Jack. Jack is 17 years old.'

除了这种按位置的接收参数方式,还可以使用按关键字的接收参数方式:

>>> 'item {a}, {and_another}'.format(a=1, and_another=1.234)
'item 1, 1.234'

它们可以在同一个格式字符串中混合使用。

由于字符串内的花括号被用来确定格式数据的位置,可以用重复的花括号“ {{ ”和“ }} ”来代表格式字符串内原始的花括号字符:

>>> 'use {{}} to format {}'.format('string')
'use {} to format string'

百分号格式字符串支持的调整精度、宽度、对齐、符号等,str.format() 也全部支持。以下是部分示例:

>>> from math import e
>>> 'natural logarithm is {:+10.4}'.format(e)
'natural logarithm is     +2.718'
>>> '{:#x}'.format(1000)
'0x3e8'

格式化的完整语法

一个花括号内可以包含许多控制用的说明,它们决定了如何对填充的内容设置格式。格式化修饰符的完整语法为:

{[key][!flag][:[[fill]align][sign][#][0][minwidth][.precision][type]]}

方括号代表里面的内容作为整体是可选的。注意它们从左至右的位置。

  • key 代表用哪一个数据来填入该位置:

它可以是整数,也可以是标识符。由于格式方法的完整结构是 str.format(self, *args, **kwargs) ,整数告诉格式字符串从 args 元组内寻找元素,标识符告诉格式字符串从 kwargs 字典内寻找元素。

这种方式直接引用的对象,甚至可以再次通过“ . ”运算符或“ [] ”运算符取值:

>>> 'use {0.__name__}, get {1[value]}'.format(
...     divmod, {'value': (10.3, 2)})
'use divmod, get (10.3, 2)'
  • !flag 是一个显式转换标志。这个显式转换标志用于最先对被格式的数据变为标准的字符串对象:

!s 会用 str() 函数处理数据,得到对象的详细信息;
!r 会用 repr() 函数处理数据,得到对象的描述信息;
!a 会用 acsii() 函数处理数据,得到 ASCII 字符串。

例如,如果要让替换的字符串加上引号,可以使用 !r 修饰它:

>>> 'The type of ... is {!r}'.format('list')
"The type of ... is 'list'"

接下来的冒号后面引导了一些标准的格式说明符(format specifier),它们有:

  • align 代表在该位置比较宽时,数据是如何对齐的。该选项提供的值有:
<左对齐
>右对齐
^居中对齐
=一种对数字的特殊对齐方式,数字右对齐,但符号在最左侧

对最后一种对齐方式的直观演示如下:

>>> 'data: {:=10}'.format(-15.02)
'data: -    15.02'
  • 如果给定了对齐方式,那么 fill 表示对齐后剩下的位置该填充什么字符。默认情况下,剩余的部分用空格代替。

对以上格式化稍作修改,它会美观很多:

>>> 'data: {:0=10}'.format(-15.02)
'data: -000015.02'
  • sign 选项告诉格式字符串如何处理数字的符号位:
+正数前面加上正号“ + ”,负数前面加上符号“ -
-只需要在负数前加上负号“ - ”即可
空格在负数前加上负号“ - ”;同时为了对齐,在正数前加一个空格
  • # 字符如果使用,可以处理不同进制的数字,在前面加上“ 0b ”、“ 0o ”、“ 0x ”等前缀。
  • minwidth 选项代表该位置的最小宽度:

前面的对齐和填充选项就是基于该最小宽度设置的。如果格式后该位置拥有的字符数仍然无法填满这个最小宽度,那么对齐和填充才有意义。

所谓的“最小”表示如果已经比这个最小值还宽了,那就可以忽略它,按正常的宽度排列内容。

还有一种特殊的情况是,如果该宽度值被写成“ 0 ”打头的数字,那么它会使用一种特殊的“零填充(zero-padding)”方式。具体来说,它等价于使用“ = ”的对齐并用“ 0 ”字符填充。例如:

>>> '{:010}'.format(-30)
'-000000030'
  • .precision 表示小数显示多少位数。如果不用在小数上,表示最多显示多少字符。
  • type 选项告诉格式字符串数据用什么类型展示:

对于整数,可以用以下选项将它解释成:

b二进制数字d(默认)十进制数字
o八进制数字c对应位置的Unicode字符
x十六进制数字n数字。相比通用的 d 选项,它可能会根据本地化做一些表示方面的调整
X全部大写的十六进制数字,包括可能的前缀

对于浮点数,可以用以下选项将它解释成:

e使用带“ e ”的科学计数法表示浮点数g(默认)哪种方式表示更清晰,就用哪种方式
E大写版本的“ eG大写版本的“ g
f使用小数点形式表示浮点数n浮点数。相比通用的 g 选项,它可能会根据本地化做一些表示方面的调整
F大写版本的“ f%百分小数格式,会在后面加上百分号

以上就是Python3字符串格式化的完整格式语法,这种语法相比百分号格式更加强大。

这种语法的强大之处就在于,甚至可以通过嵌套花括号,来动态调整格式化:

>>> '*{0:#{width}{base}}*'.format(100, width=15, base='b')
'*      0b1100100*'

使用这种格式化方式,还可以自行决定如何格式化一个对象。下文会提到这种方法。

使用f-string

Python3.6 中新增了一种语法,称为 f-string ,它在保持这种格式化语法的强大性的同时,更加简洁、直观。

f-string即在正常的字符串字面量前面加上一个“ f ”。这种字符串可以直接在表示时便被格式化:

>>> something = [1, 2, 0.6, 'hello']
>>> f'There are {something} {len(something)} things.'
"There are [1, 2, 0.6, 'hello'] 4 things."

注意,这里直接引用了上下文的对象,或者说一个表达式的值,将这个表达式的值用于格式化。

可以用上文中提到的语法对该表达式的值进行格式化处理:

>>> f'The first of them is {something[0]:=+5}'
'The first of them is +   1'

f-string 不仅可以用于这种普通的字符串,还可以用于三个引号对构成的多行字符串,具体的使用方法与普通字符串是一致的。

f-string 和原始字符串可以共用,形成一种特殊的 fr 字符串:

>>> regular_expression = r'[aei]+(?=\d)'
>>> fr'\{regular_expression}\i'
'\\[aei]+(?=\\d)\\i'

起始符 f 和 r 的顺序以及大小写都无所谓。f 修饰符会优先起作用,将字符串中的占位符换成需要的内容。

f-string 简洁强大,在大多数即格式即用的时候非常好用。

自定义格式化

可以根据自己的类型,确定如何对数据进行格式化。例如,Python内置的日期时间类型就支持一种对日期时间处理的特殊格式化:

>>> from datetime import datetime
>>> 'now is {:%Y %b %d[%a] %H:%M:%S}'.format(datetime.now())
'now is 2022 Feb 22[Tue] 20:55:33'

如果自定义的类需要自定义的字符串格式方法,那么它需要实现特殊方法 __format__() 。该方法的标准结构为:

class TypeName:
    ...
    def __format__(self, format_spec: str):
        pass

在进行字符串格式化时,冒号后面的转换说明符会传递给 format_spec 参数,这样对象就可以借助该参数中的内容,做一些合适的处理,返回一个字符串以供格式化。

注意:如果使用“ ! ”显式转换,那么经过显式转换后,该对象已经变成字符串类型,因此后面需要使用字符串的转换说明符格式。

下面提供了一个示例,给该类的使用者提供了自行格式化的方式:

class Point:
    def __init__(self, x, y, z):
        self.x, self.y, self.z = x, y, z
    def __format__(self, format_spec: str):
        return format_spec.replace('%x', str(self.x))\
                          .replace('%y', str(self.y))\
                          .replace('%z', str(self.z))

下面是一个使用示例:

p = Point(10, 20, 6.15)
print('The end of the line is at {:(%x, %y, %z)}'.format(p))

获得的结果为:

$ python -u string-format.py
The end of the line is at (10, 20, 6.15)

最后,Python3内置了一个函数 format(value, format_spec) ,本质上就是调用对象的 __format__() 方法,来表示一个对象被格式化后的结果。

至此,可以总结出Python3字符串格式化的一般规律了:

花括号对“ {} ”表示格式后的内容将放在这里;

花括号内,最开头的一部分表示需要对什么数据执行格式化。它可能是一个外部的变量、.format() 的一个参数,或它们参与的表达式计算后的结果;

接下来“ ! ”的部分决定是否需要调用显式转换,如果使用这一步,该数据将先一步按照某些方式转换为标准的字符串;

最后,“ : ”后面的部分是转换说明符,这部分内容将决定一个对象如何进行最终的格式化。对于 str 对象的转换说明符语法,可以参见前文的详细介绍,并在使用时查阅即可。

总体来说,字符串的格式化作为一种Python标准的语法,不需要深入研究它,只需要明白它的基本工作原理,真正要用到的时候只需要查阅它的细节即可。

参考资料

https://docs.python.org/3.10/library/string.html#format-string-syntax

Python3标准库 string ,介绍字符串是如何格式与被格式的

https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals

从词法分析的角度介绍了格式化字符串字面量的原理

https://docs.python.org/3/reference/datamodel.html#object.format

介绍对象的格式化模型

https://www.python.org/dev/peps/pep-3101/

一篇非常优质的关于Python3字符串格式化的讨论

京ICP备2021034974号
contact me by hello@frozencandles.fun