Python 有多种字符串格式化方法,主要包括:
- 字符串格式设置运算符(%)
- 模板字符串(Template)
str.format()
方法- f-string
%-formatting
% 称为“字符串格式设置运算符”。
% 运算符行为类似于 C 语言的 printf
函数(Java 中同样保留了该函数)。
在 Python 3.6 之后,官方推荐使用 f-string 或 str.format() 来代替 % 格式化。
- 左侧操作数,是一个“格式字符串”,指定目标字符串的格式。
- 右侧操作数,为格式字符串提示填充值。它可以是单值(如:字符串、数字等)、元组、字典。
格式字符串的完整定义形式是:%[( name )][ flags ][ width ].[ precision ] typecode
。
参数 | 必需? | 说明 |
---|---|---|
name | 可选 | 数据值以字典类型提供时,指定字典的 key |
flags | 可选 | 见附录 flags 说明 |
width | 可选 | 目标字符串占用宽度 |
precision | 可选 | 数值型数据保留的小数位数 |
typecode | 必需 | 数据值类型 |
【特殊说明】
1 flags
默认行为是:右对齐、正数不显示符号、用空格填充“空位”、八/十六进制不显示进制前缀。
2 precision
2.1 整数精度
精度作用于整数时,控制整数位数,不够补 0。(见【示例1-4】)
并且精度补的 0,不会被指定的空格前缀替换。(见【示例1-5】)2.2 浮点数精度
对于浮点数而言,当不指定精度时,是否省略
.
的行为并不相同。
不省略时,相当于指定精度为 0。(见【示例1-6】)
省略时,相当于指定最高精度 6。(见【示例1-7】)3 转义 %
%
字符是转换说明符的前缀,要原样输出,需要连写两次, 即%%
。4 限制
只支持 int/str/double 类型的格式化。
1 | # 示例1-1:单值,字符串 |
str.format()
str.format()
与 Formateter
类共享相同格式字符串语法,与下文的 f-string 相似,但更简单,关键是不支持任意表达式。
格式化字符串包含的关键部分就是以花括号括起来的替换字段(replacement field),语法形式如下:
"{" [field_name] ["!" conversion] [":" format_spec] "}"
主要包括 3 部分:
- 字段名(field name)
- 转换标志(conversion)
- 格式规格(format spec)
字段名
字段名不仅仅是个名称,可以是:
- 索引序号
- 关键字参数 Key
- 属性访问/方法调用
- 列表元素访问
字段名的值源自 format()
方法传入参数,传入参数可分为位置参数和关键字参数,分别对应未命名字段和命名字段。
如果均传入位置参数,则未命名字段内容为参数传入的位置索引。由于格式字符串编号是显式指定的,也称“手动编号”。
1 | "{0} {1} {2} {3} {0} {1}".format("to", "be", "or", "not") |
特殊地,如果未命名字段是按索引序使用的,则可以省略。没有显式指定编号,也称“自动编号”。
1 | "{}, {}!".format("Hello", "world") |
如果以关键字参数传入,则命名字段内容为关键字参数的 Key。
1 | "{hi}, {who}!".format(hi="hello", who="world") |
未命名字段与命名字段是可以混用的,但是作为未命名字段不能混用自动编号与手动编号——换句话说,只能全部使用自动编号或手动编号。
1 | # 自动编号字段与关键字字段共用 |
转换标志
转换标志,用于在格式化前将值转换为一个字符串。共有三种,!s
、!r
、!a
分别调用 str()
、repr()
、ascii()
转换。
1 | "{pi!s} {pi!r} {pi!a}".format(pi="π") |
格式规格
描述值的字段宽度、对齐方式、填充符号、小数精度等呈现细节。它由一种“格式规格迷你语言”编写。其语法形式为:
[[fill]align][sign][#][0][width][grouping_option][.precision][type]
一共分为八个部分:
fill
:填充字符,任意字符。默认为空格。需要后跟有效的align
。align
:对齐方式。候选值包括:<
>
=
^
。(详见“附录”)sign
:符号位。候选值包括:+
-
“ “(空格)。(详见“附录”)#
:替代形式转换。仅适用于数值型(整数、浮点数、复数),不同类型。(详见“附录”)0
:0
填充。即fill
和align
未指定时,将在符号与数字点填充0
。等价于fill
为 0、align
为=
。width
:最小字段宽度。grouping_option
:分组分隔符。候选值包括:_
,
。(详见“附录”)precision
:精度。type
:数据类型。
1 | # 格式说明符 |
f-string
f-string 是 Python 3.6 新引入的字符串字面值,是一种格式化字符串字面值,PEP 术语也称为“字符串插值”(Literal String Interpolation)。
它以 f 或 F 为前缀——代表“formatted string”——需运行时计算的表达式置于一对花括号中。形式如下:
1 | f ' <text> { <expression> <optional !s, !r, or !a> <optional : format specifier> } <text> ... ' |
!s
、!r
、!a
3 个称为“转换标志符”,它指明用什么“方法”转换。:
后的部分称为“格式说明符(Format Specifier)”,用于指定如何格式化变量的值。
更多示例说明请参考 Python:f-string 格式化对象
由于不存在显式传入参数,所以表达式不可能使用位置索引,只能使用当前作用域中的变量——f 字符串原理上就是转换为 format()
,因此理论上两者的格式字符串大部分是互通的——即使如此,f-string 的语法也比 format()
的格式字符串语法复杂。
【特殊说明】
1 转义花括号
由于花括号是定界符,所以需要转义——使用双花括号
{{
或}}
。2 格式表达式不允许的字符
#
及\
会导致 SyntaxError。要使用包含这些字符的值,可以创建临时变量来存储。如:newline = '\n'
则可用f'newline: {ord(newline)}'
。3 不能用作文档字符串
即使不包含表达式 f 字符串也不能用作文档字符串。
4 调试模式
表达式后加等号,会输出表达式文本、
=
、求值结果,这通常用于调试打印内容。(见【示例4-3】)
1 | # 示例4-1: |
模板字符串
模板字符串使用类 UNIX shell 语法。
1 | from string import Template |
模板字符串旨在简化基本的格式设置机制,因此,它的用法相对而言更简单。(详见“模板字符串”章节)
模板字符串支持基于 $
的替换,替换占位符格式为 $identifier
——因此命名应符合标识符规则——注意,不区分大小写。
【特殊说明】
1 另一种形式
默认情况下, 替换占位符会从
$
一直延续到第一个非标识符字符终结。因此如果其后紧跟合法标识符字符,则需要使用${identifier}
形式。2 转义
$
$
是替换占位符的前缀,要原样输出。应使用$$
转义。3 限制
无法控制指定对象到字符串的转化。
Template
类提供了两个方法将模板字符串替换为目标字符串:
1 | substitute(mapping={}, /, **kwds) |
可以使用字典或一组关键字参数传入“替换键值对”,如果同时使用,则以关键字参数组优先。
substitute vs. safe_substitute
当存在未传入的替换占位符时,
substitute
方法会抛出KeyError
;非法位置出现$
,substitute
方法会抛出ValueError
,而safe_substitute
方法静默地原样输出。【扩展说明】
如果你熟悉 PowerShell 的相关语法,模板字符串将很容易理解。
小结
从代码角度来说,除 f-string 外三个方法的一个缺点是:插入的值来源于“下文”而非“上文”。换句话说,当读到格式字符串时,我们并不能马上知道格式化后的字符串到底是什么内容,还需要继续阅读代码。f-string 则没有这个问题,其插值在模板出现之前就已经给出了。另外,f-string 速度更快。
% 格式字符串语法
%[( name )][ flags ][ width ].[ precision ] typecode
format() 替换字段语法
"{" [field_name] ["!" conversion] [":" format_spec] "}"
format() 格式规格语法
[[fill]align][sign][#][0][width][grouping_option][.precision][type]
f-string 语法
f ' <text> { <expression> <optional !s, !r, or !a> <optional : format specifier> } <text> ... '
扩展示例
1 | # 示例1:% 防御性编程 |
附录
%
运算符
flags 符号
符号 | 功能 |
---|---|
- |
左对齐 |
+ |
正数前缀显示加号(+ ) |
空格 | 正数前缀显示空格(代表符号位) |
# |
八进制前缀显示 0o 十六进制前缀显示 0x /0X |
0 |
右对齐时,“空位”填充 0 (而非空格,左对齐无效果) |
typecode 符号
符号 | 描述 |
---|---|
s |
字符串 |
d |
整数 |
f |
浮点数(可指定小数位数) |
e |
浮点数科学计数法 |
E |
作用同 %e(大写 E) |
g |
%f 和 %e 的简写 |
G |
%f 和 %E 的简写 |
u |
无符号整型 |
c |
字符及其 ASCII 码 |
o |
无符号八进制数 |
x |
无符号十六进制数 |
X |
无符号十六进制数(大写 X) |
转换标志
转换符 | 调用方法 |
---|---|
!s |
str() |
!r |
repr() |
!a |
ascii() |
注:f-string 和 str.format()
都支持。
格式规格迷你语言
对齐选项(align)
选项 | 含意 |
---|---|
< |
左对齐(大多数对象的默认值) |
> |
右对齐(数字的默认值) |
= |
在符号与数字间填充(0 在字段宽度前的默认值) |
^ |
居中对齐 |
注:只有最小字段宽度大于字段宽度时,填充与对齐才有意义。
符号位(sign)
选项 | 含意 |
---|---|
+ |
正数显示前缀 + ,负数显示前缀 - |
- |
仅负数显示前缀 - (默认行为) |
space |
正数显示前缀空格,负数显示前缀 - |
注:仅对数字类型有效。
替代形式(#)
类型 | 含意 |
---|---|
二进制整数 | 添加前缀 0b |
八进制整数 | 添加前缀 0o |
十六进制整数 | 添加前缀 0x 或 0X |
浮点/复数 | 总包含小数点 |
分隔符(grouping option)
选项 | 适用类型 | 版本 | 含意 |
---|---|---|---|
, |
十进制整数、浮点数、复数 | 3.1+ | 千位分隔符。需要随区域改变,类型应使用 n 。 |
_ |
十进制整数、浮点数,二进制、八进制、十六进制整数 | 3.6+ | 十进制整数、浮点数时用作千位分隔符,二进制、八进制、十六进制整数时每四位插入一个分隔符。 |
精度(precision)
作用类型 | 含意 |
---|---|
f F |
小数点后显示的数位 |
g G |
小数点前后总共显示的数位 |
s |
最大字段宽度 |
类型符号
符号 | 值类型 | 含意 |
---|---|---|
s |
字符串 | 字符串,字符串默认 |
b |
整数 | 二进制整数 |
c |
整数 | 字符,整数将转换为 unicode 字符 |
d |
整数 | 十进制整数 |
o |
整数 | 八进制整数 |
x |
整数 | 十六进制整数 |
X |
整数 | 十六进制整数,前缀大写 x,字母数码大写 |
n |
integer/float/Decimal | 同 d / g ,分隔字符随当前区域不同 |
e |
float/Decimal | 科学计数法 |
E |
float/Decimal | 类似 e ,大写 E 分隔字符 |
f |
float/Decimal | 定点表示法 |
F |
float/Decimal | 类似 f ,nan => NAN ,inf => INF |
g |
float/Decimal | 常规格式 |
G |
float/Decimal | 类似 g ,e 分隔字符、nan 、inf 等大写 |
% |
float/Decimal | 百分比,数值乘以 100 按 f 格式显示并加百分号 |
参考
Python doc - 常见字符串操作:格式字符串语法、模板字符串等说明。
Python doc - 格式字符串字面值:提供了 f 字符串的语法定义。
PEP 498 – Literal String Interpolation:f 字符串提案。讨论部分介绍了 f-string 与 str.format 表达式的区别、表达式中的 lambda等问题。
Python doc - datetime:提供了日期时间格式化代码。
后记
本来只是想归纳下字符串格式化的各种方法,一一列举后发现常用的也就只有 4 种,便满心欢喜的以为只需要写一篇短文就可以搞定了。
以为自己大部分知识点都已清楚了,但是,稍一深入就感觉有点不对了,没想到盲点还不少。在 flomo 回顾笔记的时候才发现,这就是被称为“解释性深度错觉”的心理偏误——人们以为自己知道的往往是不知道的。正应了那句话:要想知道自己有多无知,就选一个话题一直说下去。
因此,也深深体会到写技术文章的好处,它可以让你知道什么东西自己不知道,而借此正好去知道。