10 个 f-string 技巧:让你的 Python 代码少写一半
你还在写 "{}".format(name) 吗?或者更老土的 "%s" % name?每次多打十几个字符,换来的代码更难读、更慢。
我接手过一个项目,日志系统全是用 % 格式化拼出来的。一个请求的日志少则 5 个变量,多则 20 个,格式字符串和变量列表隔着十万八千里,改一个变量名要数 s、d、f 数到眼花。
f-string 从 Python 3.6 就有了,但绝大多数教程还停留在「基本用法」这一层。3.12 的一次底层重写(PEP 701),让它的能力翻了几倍。下面这些技巧,我赌你至少有 3 个没见过。
先说结论
f-string 是 Python 字符串格式化的事实标准——更快、更短、更强。但如果你只知道 f"{name}",那你只用了它 10% 的能力。
从调试神器 = 语法,到 3.12 解锁的同引号嵌套、反斜杠、海象运算符,到动态格式控制的嵌套写法——这些技巧每一个都能让你少写几行样板代码。
下面是我日常开发中用得最多的 10 个技巧,每个都有可运行的代码,拿来就能贴进你的项目。
技巧 1:调试神器——= 语法,一行搞定变量名+值
排 bug 时,你一定写过这种代码:
1
2
| x = 42
print(f"x = {x}") # x = 42
|
变量名打了一遍,变量本身又打一遍,纯属重复劳动。Python 3.8 给了一个语法糖:
1
2
3
4
5
| x = 42
print(f"{x=}") # x=42
print(f"{x=!r}") # x=42(repr 形式)
print(f"{x*2=}") # x*2=84
print(f"{type(x)=}") # type(x)=<class 'int'>
|
= 直接把表达式本身和结果一起输出。调试循环、列表推导时尤其好用,不用再纠结变量名叫什么。这是我用得最频繁的 f-string 技巧,没有之一。
技巧 2:数字格式化——千分位、百分比、进制一步到位
格式化数字的需求,f-string 的 format spec 基本全能覆盖:
1
2
3
4
5
6
7
8
9
10
| revenue = 1234567890
print(f"{revenue:,}") # 1,234,567,890(千分位)
ratio = 0.8567
print(f"{ratio:.2%}") # 85.67%(百分比,保留2位小数)
print(f"{255:#010x}") # 0x000000ff(十六进制,补零到8位)
page = 42
print(f"Page {page:08d}") # Page 00000042(零填充)
|
不需要 locale 模块,不需要 format() 函数,不需要 "{:.2%}".format()。一个冒号搞定。
技巧 3:对齐与填充——终端表格、日志排版利器
左对齐 <、右对齐 >、居中 ^,还能自定义填充字符:
1
2
3
4
5
6
7
8
9
10
| # 成绩表对齐
for name, score in [("Alice", 95), ("Bob", 87), ("Charlie", 100)]:
print(f"{name:<10} | {score:>5}")
# Alice | 95
# Bob | 87
# Charlie | 100
# 居中+自定义填充
print(f"{'Title':*^30}")
# ************Title*************
|
做 CLI 工具、日志输出、数据报表时,这几个符号比手写 ljust()/rjust()/center() 干净十倍。
技巧 4:日期格式化——告别 strftime
strftime 的 %Y-%m-%d 可以直接写进 f-string:
1
2
3
4
5
| from datetime import datetime
now = datetime.now()
print(f"{now:%Y-%m-%d %H:%M:%S}") # 2026-06-26 08:34:28
print(f"{now:%Y年%m月%d日}") # 2026年06月26日
|
少一层方法调用,可读性更好。日志、时间戳、报表里到处都能用。
技巧 5:性能——f-string 到底快多少
很多人说「f-string 比 % 快 3 倍」,这个说法过时了。我在 Python 3.13 上实测,差距没那么夸张,但 f-string 确实最快:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| import timeit
N = 500000
name, age = "Alice", 30
# f-string
timeit.timeit("f'{name} is {age}'", globals=globals(), number=N)
# 0.025s
# % 格式化
timeit.timeit("'%s is %d' % (name, age)", globals=globals(), number=N)
# 0.035s(慢 1.4x)
# .format()
timeit.timeit("'{} is {}'.format(name, age)", globals=globals(), number=N)
# 0.037s(慢 1.5x)
|
f-string 在编译时就生成了格式化指令,运行时直接求值,不需要像 .format() 那样在运行时解析模板字符串。复杂表达式(字典访问、属性访问)时优势更大。
对于多数项目,性能差异微乎其微——一次请求多花几微秒,用户根本感知不到。但既然 f-string 又快又短又好读,没有理由不用它。选 f-string 不是因为「快」,而是因为「快且好」,性能只是锦上添花。
技巧 6:Python 3.12 革命——PEP 701 做了什么
这是 f-string 自 3.6 引入以来最大的语法改革。3.12 之前,f-string 有一堆莫名其妙的限制。3.12 一次性全解开了:
同引号嵌套——以前 f-string 内外必须用不同引号,3.12 后同引号也行:
1
2
3
4
5
6
7
8
9
10
| # 3.12 前:外双内单,或外单内双
f"{'hello'}" # ✅ 合法
f'{"hello"}' # ✅ 合法
# 3.12 前:同引号嵌套 → 语法错误
# f"{"hello"}" # ❌ SyntaxError
# f'{'hello'}' # ❌ SyntaxError
# 3.12 后:同引号完全合法
f"{"hello"}" # ✅ 'hello'
|
反斜杠支持——以前 f-string 内部不能出现反斜杠:
1
2
3
4
5
6
7
8
9
| # 3.12 前:报错!
f"{'\n'.join(names)}"
# 3.12 后:完全合法
names = ["Alice", "Bob", "Charlie"]
f"{'\n'.join(names)}"
# Alice
# Bob
# Charlie
|
海象运算符——在 f-string 内部赋值:
1
2
| f"{(result := sum([1, 2, 3]))}" # '6'
f"{result=}" # result=6
|
多行表达式——f-string 内部的表达式可以跨行:
1
2
3
4
5
6
7
8
9
10
| # 3.12 后:换行合法
items = [
{"name": "Alice", "price": 99.5},
{"name": "Bob", "price": 150},
]
f"Total: {
sum(
item['price'] for item in items
)
}" # 'Total: 249.5'
|
注释支持——f-string 内部可以写注释,方便复杂表达式做标注。
错误信息也大幅改善——3.12 之前 f-string 出错只告诉你「这行有问题」,3.12 之后能精确指出 f-string 内部哪个表达式出错,省下大量排查时间。
一句话:如果你还在用 3.11 或更早,升级到 3.12+ 会让你的 f-string 体验质变。
技巧 7:嵌套 f-string——动态控制格式
f-string 里可以嵌套 f-string,实现「运行时决定格式」的效果:
1
2
3
4
5
6
7
8
9
10
11
12
| # 用变量控制宽度和精度
width = 15
precision = 3
value = 3.14159265
print(f"{value:{width}.{precision}f}")
# 3.142
# 生成格式化列表
items = [1, 2, 3, 4, 5]
print(f"{' | '.join(f'{x:02d}' for x in items)}")
# 01 | 02 | 03 | 04 | 05
|
第一个例子用嵌套 {width} 和 {precision} 动态控制浮点数格式。第二个例子在 join 里嵌套 f-string 生成格式化字符串。这在 3.12 之前需要混用引号才能写,现在一气呵成。
技巧 8:字典访问——3.12 前后的引号规则变化
f-string 里访问字典,引号规则在 3.12 做了改变:
1
2
3
4
5
6
7
| data = {"name": "Alice", "age": 30}
# 3.12 后:外层和 key 可以用同一种引号
f"{data['name']} is {data['age']}" # Alice is 30
# 3.12 后:同引号也行
f"{data["name"]}" # 3.12+ 才合法
|
3.12 之前,如果 f-string 用双引号 f"...",里面的字典 key 必须用单引号 data['name']。一旦你从别处复制过来一段双引号 key 的字典,就得手动改引号,很烦。
3.12 之后这个限制取消了,因为解析器从正则匹配重写成了完整的语法树解析,能正确处理任意深度的嵌套引号。
实际建议:不管什么版本,内外用不同引号永远安全。如果你的项目还要兼容 3.11 及以下,养成 f"{}" 配 key['xxx'] 的习惯。
另一个实用场景——嵌套数据结构访问:
1
2
3
4
5
6
7
8
9
| # API 响应嵌套字典
response = {
"user": {"name": "Alice", "profile": {"city": "上海"}},
"status": "active",
}
# 直接在 f-string 里逐层访问
print(f"用户 {response['user']['name']} 来自 {response['user']['profile']['city']}")
# 用户 Alice 来自 上海
|
技巧 9:实战场景——日志、错误消息、数据表格
这些是我在实际项目里用得最多的模式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # 日志格式化
level, message = "INFO", "Request completed"
print(f"[{level:>5}] {message}")
# [ INFO] Request completed
# 错误消息(带 repr)
expected, actual = [1, 2, 3], [1, 2]
print(f"Expected {len(expected)} items, got {len(actual)}")
# Expected 3 items, got 2
# 数据展示表格
print("Name Score Grade")
for name, score, grade in [("Alice", 95, "A"), ("Bob", 87, "B"), ("Charlie", 100, "A+")]:
print(f" {name:<10} {score:>5} {grade:>3}")
# Alice 95 A
# Bob 87 B
# Charlie 100 A+
|
日志、错误消息、CLI 输出——这三类场景覆盖了 90% 的 f-string 实战需求。其中 = 语法做调试、对齐符号做表格、日期格式做日志,这三个组合拳基本能搞定日常工作。
再补一个容易被忽略的技巧——结合 = 语法和 format spec,调试时直接输出格式化后的值:
1
2
3
4
5
| # 调试时同时看变量名和格式化结果(= 放在 : 前面)
pi = 3.14159265
count = 1234567
print(f"{pi=:.4f}") # pi=3.1416
print(f"{count=:,}") # count=1,234,567
|
注意语法:= 在 : 前面。这种写法在排查「为什么格式化结果不对」时特别有用——一眼看出是值的问题还是格式的问题。
技巧 10:常见陷阱——别在这些地方踩坑
反斜杠陷阱(3.11 及以下):f-string 表达式部分不能直接用反斜杠。3.12 修复了,但如果你维护老代码:
1
2
3
4
5
6
| # 3.11 及以下:报错
# f"{lines.split('\n')}"
# 解决方案:提前把分隔符存到变量
sep = "\n"
f"{lines.split(sep)}"
|
SQL 注入风险:永远不要用 f-string 拼 SQL:
1
2
3
4
5
| # 危险!SQL 注入
query = f"SELECT * FROM users WHERE name = '{user_input}'"
# 安全:参数化查询
cursor.execute("SELECT * FROM users WHERE name = ?", (user_input,))
|
f-string 是格式化工具,不是安全工具。涉及 SQL、Shell 命令时,永远用参数化接口。
长表达式可读性:f-string 里塞复杂逻辑会变成天书:
1
2
3
4
5
6
| # 难读
f"Total: {sum(x['price'] * x['qty'] for x in order['items'] if x['status'] == 'active')}"
# 好读:提前算好
total = sum(x['price'] * x['qty'] for x in order['items'] if x['status'] == 'active')
f"Total: {total}"
|
f-string 内部只放变量名和简单表达式,复杂逻辑提取到外面。这条规则没有例外。可读性永远比「少一行代码」重要。
版本兼容陷阱——用了 3.12+ 的新语法(同引号嵌套、反斜杠、多行),在 3.11 及以下会直接语法错误,不会像普通代码那样优雅降级。如果你的库要兼容旧版本,务必在 CI 里跑多个 Python 版本的测试。不确定就坚持用不同引号、避免反斜杠,这是最安全的写法。
总结
10 个技巧的优先级:先用 1-4(调试、数字、对齐、日期),日常开发立刻受益;再学 6-7(3.12 新语法、嵌套)解锁进阶能力;9-10(实战+陷阱)随时翻阅。
如果你还在 3.11 或更早,强烈建议升到 3.12+。PEP 701 对 f-string 的改造是质变,用过就回不去。
f-string 不只是「另一种格式化方式」,它是 Python 字符串处理的终极形态。掌握它,你的代码会少写一半,好读一倍。
最后给一个迁移建议:如果你项目里还有大量 % 或 .format(),不要一次性重构。下次改到那行代码时顺手换成 f-string,几个月下来整个项目就干净了。代码迁移最大的敌人从来不是技术难度,而是「改一下整个文件」的心理阻力。
文 / varkm
关注 varkm,一起学习,一起成长