Python 字符串格式化全解析:%、format() 与 f-string 的前世今生
字符串格式化是程序开发中不可或缺的基础能力,它负责将变量、表达式等动态内容嵌入固定文本模板中,生成人类可读的字符串。Python 提供了三种主流的格式化方式:
1. 传统的 % 占位符
2. 功能丰富的 str.format() 方法
3. 现代简洁的 f-string(格式化字符串字面值)
这三种方式各有设计背景、语法特点和适用场景。深入理解它们的差异与联系,能帮助开发者写出更高效、更易维护的代码。
一、% 格式化:Python 格式化的“元老”
% 格式化是 Python 最早引入的字符串格式化机制,其设计灵感源自 C 语言的 printf() 函数。对于熟悉 C 语言的开发者,上手成本低。
1.1 基本语法与占位符
核心语法:模板字符串 % (值1, 值2, ...)
常见占位符对照表
占位符 | 含义 | 示例 |
%s | 字符串(调用 str()) | %s → "hello" |
%r | 原始表示(调用 repr()) | %r → "'hello'" |
%d | 十进制整数 | %d → 42 |
%f | 浮点数 | %f → 3.141593 |
%x/%X | 十六进制整数 | %x → 2a,%X → 2A |
%% | 字面量 % | %% → "%" |
代码示例
Python name = "Alice" age = 30 print("Name: %s, Age: %d" % (name, age)) # 输出:Name: Alice, Age: 30
score = 95.5 print("Score: %f" % score) # 输出:Score: 95.500000 |
1.2 进阶用法:格式控制
语法:%[宽度][.精度][类型]
Python pi = 3.1415926535 print("PI: %.2f" % pi) # 保留2位小数,输出:PI: 3.14
num = 42 print("Number: %5d" % num) # 右对齐,宽度5,输出:Number: 42 print("Number: %-5d" % num) # 左对齐,宽度5,输出:Number: 42
text = "Hello, World!" print("Text: %.5s" % text) # 截断到5个字符,输出:Text: Hello |
1.3 局限性
1. 类型严格匹配,易抛出类型错误
2. 多变量需封装为元组,顺序错误会导致输出混乱
3. 不支持嵌套表达式或直接调用对象方法
4. 扩展性差,无法实现自定义格式逻辑
Python # 类型不匹配报错 print("Age: %d" % "30") # TypeError: %d format: a real number is required, not str
# 多变量顺序易错 age = 30 name = "Alice" print("%s is %d years old" % (age, name)) # 输出混乱:30 is Alice years old |
二、str.format():功能升级的继承者
Python 2.6 引入 str.format(),Python 3 完善了该机制。它使用 {} 作为占位符,支持位置参数、关键字参数、嵌套访问和自定义对象格式化,解决了 % 格式化的诸多痛点。
2.1 基本语法与参数传递
核心语法:模板字符串.format(参数1, 参数2, ..., 关键字参数1=值1, ...)
位置参数
Python # 按顺序匹配 print("Name: {}, Age: {}".format("Bob", 25)) # 输出:Name: Bob, Age: 25
# 按索引匹配,支持重复使用 print("Second: {1}, First: {0}, First again: {0}".format("A", "B")) # 输出:Second: B, First: A, First again: A |
关键字参数
Python # 按参数名匹配,无需关注顺序 print("User: {name}, ID: {id}".format(name="Charlie", id=1001)) # 输出:User: Charlie, ID: 1001 |
混合使用
Python # 位置参数在前,关键字参数在后 print("{}: {name} ({age})".format("Info", name="David", age=28)) # 输出:Info: David (28) |
2.2 进阶用法:格式规范与类型转换
对齐与宽度
语法:{:[对齐方式][宽度]},对齐方式包括 <(左对齐)、^(居中)、>(右对齐)
Python print("|{:<10}|{:^10}|{:>10}|".format("Left", "Center", "Right")) # 输出:|Left | Center | Right| |
浮点数精度 & 千位分隔符
Python pi = 3.1415926535 print("PI: {:.3f}".format(pi)) # 保留3位小数,输出:PI: 3.142
num = 1234567 print("Number: {:,}".format(num)) # 千位分隔符,输出:Number: 1,234,567 |
类型转换
Python text = "Hello" print("str: {!s}, repr: {!r}".format(text, text)) # 输出:str: Hello, repr: 'Hello' |
日期 & 百分比
Python from datetime import datetime
now = datetime(2025, 11, 12, 15, 30) print("Date: {:%Y-%m-%d %H:%M}".format(now)) # 输出:Date: 2025-11-12 15:30
ratio = 0.753 print("Ratio: {:.1%}".format(ratio)) # 格式化为百分比,保留1位小数,输出:Ratio: 75.3% |
2.3 高级特性:嵌套与自定义对象
嵌套结构访问(字典、列表)
Python person = {"name": "Eve", "age": 32} hobbies = ["reading", "hiking"]
print("Name: {p[name]}, Hobby: {h[0]}".format(p=person, h=hobbies)) # 输出:Name: Eve, Hobby: reading |
自定义对象格式化(实现 __format__ 方法)
Python class Temperature: def __init__(self, c): self.celsius = c def __format__(self, spec): # spec 接收格式化指令,自定义摄氏度/华氏度转换 return f"{self.celsius:.1f}°C" if spec != 'f' else f"{self.celsius*9/5+32:.1f}°F"
temp = Temperature(25) print("Temp: {t:c}".format(t=temp)) # 输出:Temp: 25.0°C print("Temp: {t:f}".format(t=temp)) # 输出:Temp: 77.0°F |
2.4 优缺点总结
优点
1. 语法灵活,无需严格匹配变量类型
2. 格式控制能力丰富,支持多种特殊格式(千位分隔符、日期等)
3. 支持嵌套访问和自定义对象格式化,扩展性强
4. 兼容 Python 2.6+ 和所有 Python 3 版本
缺点
1. 语法相对冗长,变量较多时模板字符串可读性下降
2. 复杂场景下书写繁琐,效率低于 f-string
三、f-string:现代 Python 的最优解
Python 3.6+ 引入 f-string(格式化字符串字面值),只需在字符串前加 f 或 F,即可直接在 {} 中嵌入变量或表达式,实现模板与值的无缝融合,是目前最推荐的格式化方式。
3.1 基本语法
Python name = "Frank" age = 27 print(f"Name: {name}, Age: {age}") # 输出:Name: Frank, Age: 27
x, y = 10, 20 print(f"Sum: {x+y}, Product: {x*y}") # 直接嵌入表达式,输出:Sum: 30, Product: 200 |
3.2 格式控制
f-string 兼容 str.format() 的所有格式规范,语法更简洁:{变量/表达式:[格式规范]}
Python name = "Grace" # 对齐与宽度 print(f"|{name:<10}|{name:^10}|{name:>10}|") # 输出:|Grace | Grace | Grace|
pi = 3.14159 ratio = 0.62 # 浮点数精度与百分比 print(f"PI: {pi:.2f}, Ratio: {ratio:.0%}") # 输出:PI: 3.14, Ratio: 62%
from datetime import datetime now = datetime.now() # 日期格式化 print(f"Today: {now:%Y年%m月%d日}") # 输出:Today: 2026年01月30日(随当前日期变化) |
3.3 高级特性
函数调用
Python def greet(n): return f"Hello, {n}!"
print(f"{greet('Henry')}") # 直接调用函数,输出:Hello, Henry! |
条件表达式
Python score = 85 print(f"Result: {'Pass' if score>=60 else 'Fail'}") # 输出:Result: Pass |
嵌套结构访问
Python person = {"name": "Ivy", "age": 29} hobbies = ["painting", "coding"]
print(f"Name: {person['name']}, Hobby: {hobbies[1]}") # 输出:Name: Ivy, Hobby: coding |
多行 f-string
使用三引号包裹,保留换行格式,注意 {} 内的语法正确性
Python name = "Jack" age = 33 bio = f""" User Profile: - Name: {name} - Age: {age} - Status: {'Active' if age<40 else 'Inactive'} """ print(bio.strip()) # strip() 去除首尾多余换行 |
3.4 性能与安全
性能对比
f-string 在编译时解析,无需运行时额外处理,性能优于 % 格式化和 str.format()
Python import timeit
def test_percent(): return "%s" % "test"
def test_format(): return "{}".format("test")
def test_fstring(): return f"{'test'}"
print("Percent:", timeit.timeit(test_percent, number=10_000_000)) print("Format:", timeit.timeit(test_format, number=10_000_000)) print("F-string:", timeit.timeit(test_fstring, number=10_000_000)) |
注意事项
1. 转义 {}:若需在 f-string 中显示字面量 { 或 },需使用 {{ 或 }} 进行转义
2. 模板复用:f-string 直接绑定当前变量,无法像 str.format() 那样复用模板,需封装为函数实现复用
3. 安全风险:避免嵌入不可信用户输入,防止代码注入(所有格式化方式均需注意此点)
Python # 转义示例 print(f"Literal {{}}: {{123}}") # 输出:Literal {}: {123} |
四、三种方式的对比与选型指南
4.1 特性对比表
特性 | % 格式化 | str.format() | f-string |
语法简洁 | 中等 | 中等 | ⭐⭐⭐⭐⭐ |
可读性 | 差 | 好 | ⭐⭐⭐⭐⭐ |
功能丰富 | 基础 | 丰富 | 丰富 |
表达式支持 | ❌ | ⚠️(有限支持) | ✅ |
自定义格式 | ❌ | ✅ | ✅ |
性能 | 中等 | 中等 | ✅ |
版本兼容 | Python2/3 | 2.6+/3+ | 3.6+ |
模板复用 | ❌ | ✅ | ⚠️(需函数封装) |
4.2 场景化选型
1. 日常开发(Python 3.6+):f-string
○ 原因:简洁、可读、性能佳,满足绝大多数场景需求
2. 兼容旧版本(Python 2.x 或 3.5 及以下):str.format()
○ 原因:功能灵活,兼容性强,无类型匹配痛点
3. 日志系统:% 格式化
○ 原因:日志模块(如 logging)对 % 格式化有优化,支持延迟执行,提升性能
4. 动态模板 / 配置文件:str.format()
○ 原因:支持模板与数据分离,可多次复用同一模板进行不同值替换
5. 表达式计算、函数调用嵌入:f-string
○ 原因:可直接嵌入任意合法 Python 表达式,无需额外封装,书写高效
五、总结:格式化方式的演进与最佳实践
5.1 演进核心逻辑
Python 字符串格式化的演进体现了语言设计对“开发者体验”的持续优化:
1. % 格式化:奠定基础,满足简单场景,兼容传统 C 语言习惯
2. str.format():弥补 % 格式化的不足,提升灵活性和扩展性
3. f-string:在简洁性、可读性和性能上达到平衡,成为现代 Python 开发的首选
5.2 最佳实践
1. 统一项目格式化风格,避免多种方式混用,提升代码可维护性
2. 简单场景优先使用 f-string,复杂格式(如动态模板)使用 str.format()
3. 自定义类可实现 __str__ (默认字符串表示)与 __format__ (自定义格式化)方法,提升对象的易用性
4. 循环中避免重复生成格式化模板,可提前定义模板变量(str.format())或封装为函数(f-string),提升执行效率
5. 掌握三种方式的核心差异,可灵活应对不同版本环境和业务场景的需求