字符串格式化
将数据嵌入字符串是编程中最频繁的操作之一。Python 提供了三条技术路线:旧式的 % 运算符、面向对象的 str.format() 方法,以及 Python 3.6 起成为首选的 f-string(格式化字符串字面值)。三者底层共享同一套格式规范迷你语言,但 f-string 在可读性和执行效率上全面领先。
旧式 % 格式化
% 运算符模拟 C 语言的 printf 风格,左侧是含转换说明符的模板字符串,右侧是要插入的值(单个对象或元组)。
import math
print('The value of pi is approximately %5.3f.' % math.pi)
# The value of pi is approximately 3.142.
常用转换说明符:
%s—— 调用str()转换。%r—— 调用repr()转换。%d/%i—— 有符号十进制整数。%f—— 浮点数,默认 6 位小数;%5.3f表示总宽度 5、小数 3 位。%x/%X—— 十六进制。%%—— 字面百分号。
右侧为多个值时必须用元组包裹,字典则配合 %(name)s 语法:
data = {'name': 'Alice', 'score': 92}
print('%(name)s got %(score)d points' % data)
% 格式化在 Python 3 中仍可用,但官方文档已将其标记为旧式方法。其缺陷在于:类型匹配不严格(传错类型可能静默失败或输出意外结果)、元组单元素时的逗号陷阱、以及可读性较差。新项目应优先使用 f-string。
str.format() 方法
str.format() 使用花括号 {} 作为占位符,支持位置索引、关键字名称、甚至属性访问和元素索引,表达能力远超 %。
# 位置参数
print('{0} and {1}'.format('spam', 'eggs')) # spam and eggs
print('{1} and {0}'.format('spam', 'eggs')) # eggs and spam
# 关键字参数
print('This {food} is {adjective}.'.format(
food='spam', adjective='absolutely horrible'))
# 混合使用
print('The story of {0}, {1}, and {other}.'.format(
'Bill', 'Manfred', other='Georg'))
当格式字符串较长时,直接传入字典并用方括号访问键,或更优雅地用 ** 解包字典,可避免冗长的位置编号:
table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
# 方括号访问
print('Jack: {0[Jack]:d}; Sjoerd: {0[Sjoerd]:d}'.format(table))
# ** 解包,字段名直接对应键名
print('Jack: {Jack:d}; Sjoerd: {Sjoerd:d}; Dcab: {Dcab:d}'.format(**table))
与 vars() 配合,可以一键格式化当前作用域的所有局部变量,在调试日志中极为实用:
x = 10
y = 20
print('x={x}, y={y}'.format(**vars())) # x=10, y=20
str.format() 的真正威力在于格式规范。花括号内冒号后的部分遵循与 f-string 相同的迷你语言,例如 {0:2d} {1:3d} {2:4d} 可生成整齐的数据列。
f-string:首选方案
在字符串引号前加 f 或 F,即可在 {} 中直接嵌入任意 Python 表达式。表达式在运行时被求值,结果按格式说明格式化后插入字符串。
year = 2016
event = 'Referendum'
print(f'Results of the {year} {event}')
# Results of the 2016 Referendum
f-string 支持完整的表达式,不限于变量名:
import math
r = 5
print(f'Area = {math.pi * r ** 2:.2f}') # Area = 78.54
print(f'Next year = {year + 1}') # Next year = 2017
转换标志
在表达式后、冒号前,可用 !s、!r、!a 分别调用 str()、repr()、ascii() 对值进行前置转换:
animals = 'eels'
print(f'My hovercraft is full of {animals}.') # eels
print(f'My hovercraft is full of {animals!r}.') # 'eels'
自说明表达式(= 说明符)
在表达式末尾加 =,f-string 会同时输出表达式文本和求值结果,调试时无需手写两遍:
bugs = 'roaches'
count = 13
print(f'Debugging {bugs=} {count=} {area=}')
# Debugging bugs='roaches' count=13 area='living room'
注意:如果表达式包含空格、运算符或属性访问,= 会尽量保留原始文本形式。
格式规范迷你语言
格式规范写在冒号之后,结构为:
[[fill]align][sign][#][0][width][grouping_option][.precision][type]
对齐与填充
<左对齐,>右对齐,^居中,=仅对数字有效,在符号后填充零。- 对齐符号前可指定填充字符,默认是空格。
print(f'{"text":>10}') # ' text'
print(f'{"text":-<10}') # 'text------'
print(f'{"text":*^10}') # '***text***'
print(f'{123:0>8}') # '00000123'
符号与进制
+强制正数也显示符号;-仅负数显示(默认);空格表示正数前留空格,便于对齐。#为二进制、八进制、十六进制添加前缀0b/0o/0x。
print(f'{42:+d}') # +42
print(f'{42:d} {42:d}'.format(42, -42)) # '42 -42' ← 正数前有空格
print(f'{255:#x}') # 0xff
print(f'{255:b}') # 11111111
print(f'{255:#010b}') # 0b11111111 ← 宽度 10,含前缀
数字分组与精度
,按千位分组;_同样支持(Python 3.6+)。.precision对浮点数是小数位数,对字符串是最大字符数。
print(f'{1234567890:,}') # 1,234,567,890
print(f'{1234567890:_}') # 1_234_567_890
print(f'{math.pi:.3f}') # 3.142
print(f'{"hello world":.5}') # hello
类型码
| 类型 | 含义 |
|---|---|
d | 十进制整数 |
f / F | 定点浮点数;F 把 nan/inf 转为大写 |
e / E | 科学计数法 |
g / G | 自动选择 f 或 e |
b / o / x / X | 二进制、八进制、十六进制 |
% | 百分比,自动乘 100 |
c | 将整数转为对应 Unicode 字符 |
print(f'{0.0256:.2%}') # 2.56%
print(f'{65:c}') # A
print(f'{1e6:.2e}') # 1.00e+06
日期格式化
对 datetime 对象,格式规范委托给对象的 __format__ 方法,使用 strftime 风格代码:
from datetime import datetime
dt = datetime(2024, 6, 1, 14, 30, 0)
print(f'{dt:%Y-%m-%d %H:%M:%S}') # 2024-06-01 14:30:00
print(f'{dt:%A, %B %d}') # Saturday, June 01
常用日期代码:%Y 四位年份,%m 月份,%d 日期,%H 24小时,%M 分钟,%S 秒,%A 星期全称,%B 月份全称。
性能与选择建议
f-string 在运行时直接求值并编译为高效的字符串构建字节码,速度显著快于 % 和 str.format()。除非需要维护 Python 3.5 及以下代码,否则新代码应无条件使用 f-string。str.format() 仍适用于模板字符串需要与数据分离的场景(如国际化翻译文件)。% 格式化仅建议在修改遗留代码时保持一致性。
总结
字符串格式化的核心在于格式规范迷你语言:对齐、宽度、填充、精度、类型码、分组符等规则在 %(有限支持)、str.format() 和 f-string 中一脉相承。f-string 凭借内联表达式、= 调试语法和最优性能,成为 Python 3.6+ 的绝对首选。掌握 {expression:fill_align_width.precision_type} 这一通用模式,即可覆盖绝大多数格式化需求。