Python 格式化输出指南
在 Python 中,格式化输出是将变量或值插入到字符串中的过程,以便创建更具可读性、更动态的文本。Python 提供了多种方法来实现这一点,它们在不同版本中演进,各有优劣。
我们将详细探讨以下四种主要方法:
- f-strings(格式化字符串字面值):现代首选
str.format()
方法:功能强大,仍然常用%
操作符(C 风格格式化):旧式方法,仍在部分场景使用string.Template
类:特定场景使用,安全性高
f-strings(格式化字符串字面值)
f-strings 是在 Python 3.6 中引入的,是目前最推荐、最直观、性能最好的字符串格式化方法。
核心语法
在字符串字面值前加上 f
或 F
,然后在字符串内部使用花括号 {}
来包裹变量或表达式。
name = "世界"
year = 2025
pi = 3.1415926
# 基本用法
print(f"你好, {name}!")
# -> 你好, 世界!
# 可以直接嵌入表达式
print(f"明年是 {year + 1} 年。")
# -> 明年是 2026 年。
# 可以调用函数
print(f"你好, {name.upper()}!")
# -> 你好, 世界!
# 甚至可以创建复杂的对象
from datetime import datetime
print(f"现在时间是: {datetime.now()}")
# -> 现在时间是: 2025-07-24 09:46:20.123456 (示例)
格式规范迷你语言 (Format Specifier Mini-Language)
f-strings 的真正威力在于花括号内的“格式规范”。语法是:{value:specifier}
,其中 specifier
的通用结构是:[[fill]align][sign][#][0][width][,][.precision][type]
。
让我们逐一分解这些选项。
fill
和 align
(填充与对齐)
fill
:用于填充的单个字符(默认为空格)。align
<
:左对齐 (默认);>
:右对齐;^
:居中对齐;=
:(仅对数字有效)将填充字符放在符号和数字之间。
text = "Python"
# 左对齐,使用*填充,总宽度为10
print(f"'{text:*<10}'") # -> 'Python****'
# 右对齐,使用-填充,总宽度为10
print(f"'{text:->10}'") # -> '----Python'
# 居中对齐,使用#填充,总宽度为10
print(f"'{text:#^10}'") # -> '##Python##'
number = -123.45
# '=' 对齐,使用0填充,总宽度为10
print(f"'{number:0=10}'") # -> '-00123.45'
sign
(符号显示)
+
:始终显示符号(正数前加+
,负数前加-
);-
:仅在负数前显示符号(默认);-
。
positive = 42
negative = -42
print(f"带'+'号: {positive:+}, {negative:+}") # -> 带'+'号: +42, -42
print(f"默认'-'号: {positive}, {negative}") # -> 默认'-'号: 42, -42
print(f"带' '号: {positive: }, {negative: }") # -> 带' '号: 42, -42
#
(替代形式):对于整数类型,#
会在输出前加上 0b
(二进制)、0o
(八进制)或 0x
(十六进制)。
num = 255
print(f"二进制: {num:b}, 十六进制: {num:x}") # -> 二进制: 11111111, 十六进制: ff
print(f"替代形式: {num:#b}, {num:#x}, {num:#o}") # -> 替代形式: 0b11111111, 0xff, 0o377
0
(零填充):0
是一个简写,等效于 fill='0'
和 align='='
。它会用 0
来填充数字的左侧以达到指定的宽度。
num = 42
print(f"'{num:05}'") # -> '00042'
# 等效于
print(f"'{num:0=5}'") # -> '00042'
width
(宽度):一个整数,定义了输出的最小总宽度。如果值的长度小于 width
,则会用 fill
字符填充。
print(f"|{'Hi':10}|") # -> |Hi |
print(f"|{'Hi':>10}|") # -> | Hi|
,
或 _
(千位分隔符):用于数字,使其更易读。
large_number = 1000000000
print(f"{large_number:,}") # -> 1,000,000,000
print(f"{large_number:_}") # -> 1_000_000_000 (在代码中也是合法的数字字面量)
.precision
(精度)
- 对于浮点数 (
f
,F
,e
,E
):表示小数点后的位数; - 对于通用类型 (
g
,G
):表示有效数字的总位数; - 对于字符串:表示输出的最大字符数。
pi = 3.1415926535
# 浮点数精度
print(f"{pi:.2f}") # -> 3.14
# 通用类型精度
print(f"{pi:.5g}") # -> 3.1416 (5位有效数字,会四舍五入)
# 字符串截断
message = "你好, 这是一个很长的消息"
print(f"{message:.5s}") # -> 你好, 这
type
(类型):指定值的输出类型。
- 字符串
s
; - 整数
d
:十进制整数;b
:二进制;o
:八进制;x
/X
:十六进制(小写/大写字母);c
:字符(将整数转换为对应的 Unicode 字符)。
- 浮点数
f
/F
:定点表示法(默认精度为 6);e
/E
:科学计数法;g
/G
:通用格式。自动在定点和科学计数法之间选择(当指数小于 -4 或不小于精度时使用科学计数法);%
:百分比。将数字乘以 100 并以定点格式(f
)显示,后面跟一个%
。
# 整数类型
num = 97
print(f"十进制: {num:d}, 二进制: {num:b}, 字符: {num:c}") # -> 十进制: 97, 二进制: 1100001, 字符: a
# 浮点数类型
value = 12345.6789
print(f"定点: {value:f}") # -> 12345.678900
print(f"科学计数: {value:e}") # -> 1.234568e+04
print(f"通用: {value:g}") # -> 12345.7
# 百分比
ratio = 0.765
print(f"成功率: {ratio:.1%}") # -> 成功率: 76.5%
f-strings 高级特性
自我记录表达式 (Python 3.8+):使用 =
可以方便地进行调试,它会自动打印出表达式和它的值。
user = "admin"
is_logged_in = True
print(f"{user=} {is_logged_in=}")
# -> user='admin' is_logged_in=True
转换标志:在花括号内的变量名后使用 !
可以改变其表示形式:
!s
:调用str()
(默认);!r
:调用repr()
;!a
:调用ascii()
。
text = "你好"
print(f"{text!s}") # -> 你好
print(f"{text!r}") # -> '你好' (包含引号)
print(f"{text!a}") # -> '\u4f60\u597d' (非ASCII字符的转义表示)
嵌套和引号:f-strings 可以嵌套,但必须使用不同的引号。
width = 10
precision = 3
value = 12.34567
# 动态设置格式
print(f"结果: {value:{width}.{precision}f}") # -> 结果: 12.346
# 注意引号的使用
print(f'{"这是一个 \"引用的\" 字符串":^30}')
# -> ' 这是一个 "引用的" 字符串 '
str.format()
方法
这是在 Python 2.6 中引入的,并在 Python 3 中得到改进。在 f-strings 出现之前,它是格式化字符串的首选方法。它比 %
操作符更灵活、更强大。
核心语法
template_string.format(arg1, arg2, ...)
。
占位符类型
自动编号/位置参数
# 自动编号 (Python 2.7+ and 3.1+)
print("你好, {}! 今天是 {}。".format("张三", "星期三"))
# -> 你好, 张三! 今天是 星期三。
# 手动编号
print("你好, {0}! 今天是 {1}。{1}天气不错。".format("张三", "星期三"))
# -> 你好, 张三! 今天是 星期三。星期三天气不错。
关键字参数
print("你好, {name}! 你的年龄是 {age}。".format(name="李四", age=30))
# -> 你好, 李四! 你的年龄是 30。
访问属性和索引
class Person:
def __init__(self, name):
self.name = name
p = Person("王五")
data = ["坐标", (10, 20)]
print("姓名: {p.name}, 类型: {d[0]}, 坐标: {d[1][0]}".format(p=p, d=data))
# -> 姓名: 王五, 类型: 坐标, 坐标: 10
格式规范
str.format()
使用的格式规范迷你语言与 f-strings 完全相同。你只需要将它放在占位符的冒号后面。
pi = 3.14159
# 对齐和填充
print("'{:*>10}'".format("hi")) # -> '********hi'
# 数字格式化
print("圆周率约等于 {:.2f}".format(pi)) # -> 圆周率约等于 3.14
# 组合使用
print("用户ID: {id:04d}, 余额: ${balance:,.2f}".format(id=7, balance=1234.5))
# -> 用户ID: 0007, 余额: $1,234.50
何时使用 str.format()
?
尽管 f-strings 更简洁,但在某些情况下 str.format()
仍然很有用:比如格式化字符串模板和数据是分开定义的时候。例如,模板存储在配置文件中,而数据在运行时才生成。
# template.txt 文件内容: "Hello, {name}!"
with open('template.txt', 'r') as f:
template = f.read()
print(template.format(name="世界"))
# -> Hello, 世界!
%
操作符(C 风格格式化)
这是从 C 语言的 printf
函数借鉴过来的最古老的格式化方法。它简单易用,但在处理多个参数和不同类型时容易出错。
核心语法
"format_specifier" % (values)
。
- 如果只有一个值,可以省略括号;
- 如果有多个值,必须使用元组
()
。
name = "赵六"
age = 55
# 单个值
print("你好, %s" % name)
# -> 你好, 赵六
# 多个值 (必须用元组)
print("姓名: %s, 年龄: %d" % (name, age))
# -> 姓名: 赵六, 年龄: 55
格式化标志
标志紧跟在 %
之后。
%s
:字符串(使用str()
转换);%r
:字符串 (使用repr()
转换);%d
或%i
:有符号十进制整数;%f
:浮点数;%e
/%E
:科学计数法的浮点数;%o
:八进制数;%x
/%X
:十六进制数(小写/大写);%%
:字面量%
。
宽度和精度
语法为 %[flags][width][.precision]type
。
width
:最小字段宽度;.precision
:小数点后的位数(对浮点数)或最大字符数(对字符串);flags
-
:左对齐;+
:显示符号;0
:零填充。
pi = 3.1415926
print("ID: %04d" % 7) # -> ID: 0007
print("浮点数: %.2f" % pi) # -> 浮点数: 3.14
print("左对齐: |%-10s|" % "test") # -> 左对齐: |test |
print("右对齐: |%10s|" % "test") # -> 右对齐: | test|
字典作为参数
当格式化字符串中包含很多变量时,使用字典可以提高可读性。
data = {"name": "孙七", "balance": 99.9}
print("你好, %(name)s. 你的余额是 %(balance).2f" % data)
# -> 你好, 孙七. 你的余额是 99.90
何时使用 %
操作符?
- 遗留代码:维护旧的 Python 2/3 代码库时会经常遇到;
logging
模块:Python 的标准日志模块内部优化了%
格式化,它只在日志消息需要被实际发出时才执行格式化,从而提高性能。
import logging
logging.warning("处理用户 %s 时发生错误", "admin") # 推荐写法
# logging.warning(f"处理用户 {'admin'} 时发生错误") # 不推荐,因为f-string会立即求值
string.Template
类
这是最简单、最安全的格式化方法,但功能也最有限。它主要用于处理由用户提供的模板字符串,因为它不执行任何代码,可以防止恶意注入。
核心语法
使用 $
来标记占位符。
$variable
:简单形式;${variable}
:当占位符后紧跟字母数字字符时使用。
from string import Template
template_str = "你好, $name! 欢迎来到 ${location}。"
template = Template(template_str)
# 使用 substitute()
output = template.substitute(name="周八", location="我的世界")
print(output)
# -> 你好, 周八! 欢迎来到 我的世界。
# 如果缺少键,substitute() 会抛出 KeyError
try:
template.substitute(name="周八")
except KeyError as e:
print(f"错误: {e}") # -> 错误: 'location'
# 使用 safe_substitute()
# 如果缺少键,它会保留占位符
output_safe = template.safe_substitute(name="周八")
print(output_safe)
# -> 你好, 周八! 欢迎来到 ${location}。
string.Template
不支持任何复杂的格式化(如对齐、精度等)。
总结与建议
特性 | f-strings (推荐) | str.format() |
% 操作符 |
string.Template |
---|---|---|---|---|
可读性 | 极高 | 良好 | 一般 | 良好 |
性能 | 最快 | 较快 | 较慢 | 最慢 |
简洁性 | 非常简洁 | 较冗长 | 一般 | 冗长(需实例化) |
功能性 | 非常强大 | 非常强大 | 有限 | 非常有限 |
安全性 | 一般(可执行代码) | 一般(可执行代码) | 一般 | 最高(不执行代码) |
引入版本 | Python 3.6+ | Python 2.6+ / 3.0+ | Python 2 & 3 | Python 2.4+ |
最终建议:
- 首选 f-strings:在所有支持 Python 3.6+ 的新项目中,始终使用 f-strings。它们更易读、更简洁、性能也最好。
str.format()
作为次选:当你的格式化模板需要与数据分离时(例如,从文件或数据库加载模板),str.format()
是一个绝佳的选择。- 谨慎使用
%
操作符:仅在维护需要兼容旧版本的遗留代码,或在使用logging
等特定库时使用它。 string.Template
用于特定安全场景:当处理来自不可信来源(如用户输入)的模板时,使用string.Template
来防止安全漏洞。