Python 字符串格式化有几种方式?f-string 和 % / format 有什么区别?
简化版
**Python 字符串格式化主要有四种方式,推荐用 f-string(Python 3.6+):① % 格式化(老式,"%s=%d" % (name, age),类似 C 的 printf);② str.format()("{}={}".format(name, age),用 {} 占位);③ f-string(f-字符串,f"{name}={age}",把表达式直接写进大括号,最简洁、最快);④ 模板字符串 string.Template($name 占位,安全但功能弱,用于用户提供模板的场景)。**f-string 的优势:① 直观——变量/表达式直接写在 {} 里,一眼看懂;② 快——编译期就解析成拼接,运行时最快;③ 功能全——支持格式规格({x:.2f}、{n:,}、{x:>10})、{x!r} 转 repr、{x=} 调试(3.8+,打印 x=值)。格式规格 mini-language(三种方式共用)::.2f 保留 2 位小数、:, 千分位、:>10/:<10/:^10 右/左/居中对齐补宽、:05d 补零、:x 十六进制、:% 百分比。核心记忆:优先用 f-string(简洁快功能全),格式规格 {值:格式} 三种方式通用;% 是老式、Template 用于安全场景。
详细版
四种格式化方式对比:
| 方式 | 语法 | 特点 |
|---|---|---|
% 格式化 | "%s is %d" % (s, n) | 老式、类 C,简单但易错 |
str.format() | "{} is {}".format(s, n) | 灵活、可复用模板 |
| f-string | f"{s} is {n}" | 最简洁、最快、功能全(3.6+) |
Template | Template("$s").substitute(s=x) | 安全、用于用户模板 |
name, age, pi = "Tom", 25, 3.14159
# ① % 格式化(老式)
print("%s is %d" % (name, age)) # Tom is 25
print("%.2f" % pi) # 3.14
# ② str.format()
print("{} is {}".format(name, age)) # 位置
print("{n} is {a}".format(n=name, a=age)) # 命名
print("{0} {1} {0}".format("a", "b")) # 索引复用 → a b a
# ③ f-string(推荐)
print(f"{name} is {age}") # Tom is 25
print(f"{name} is {age * 2}") # 表达式:Tom is 50
print(f"{pi:.2f}") # 3.14
print(f"{age=}") # age=25(3.8+ 调试)
print(f"{name!r}") # 'Tom'(repr)
# 格式规格(三种方式通用的 mini-language)
print(f"{1234567:,}") # 1,234,567(千分位)
print(f"{0.25:.1%}") # 25.0%(百分比)
print(f"{42:>10}") # 右对齐宽10:" 42"
print(f"{42:05d}") # 补零:"00042"
print(f"{255:x}") # ff(十六进制)
⚠️ f-string 之所以最快,是因为它在「编译期」就被解析成字符串拼接操作,运行时不需要像
%或.format()那样解析格式字符串、查找参数——所以既最快又最直观。但要注意几个坑:① f-string 的表达式在定义时立即求值(不是延迟到打印时),所以它不能像Template那样存起来当「可复用模板」;② 不要用 f-string 拼接不可信输入到 SQL / shell 命令(那是注入漏洞,SQL 要用参数化查询、shell 要用参数列表);③ 日志建议用logging的%惰性格式(log.info("x=%s", x)只在真要输出时才格式化,比log.info(f"x={x}")省开销);④ f-string 里想要字面大括号用{{}}。选型:日常输出/拼接一律 f-string;用户提供的模板文本用Template(.format会执行{}里的属性访问、有风险);日志用%惰性。
完整版教学
一、四种格式化方式
先认识四种方式各自的样子:
① % 格式化(printf 风格,最老):
"%s" % x # 字符串
"%d" % n # 整数
"%.2f" % f # 浮点2位
"%s and %s" % (a, b) # 多个用元组
来源:C 的 printf,Python 元老级语法
② str.format()(Python 2.6+):
"{}".format(x) # 自动位置
"{0} {1}".format(a, b) # 索引
"{name}".format(name=x) # 命名
"{:.2f}".format(f) # 格式规格
灵活,模板可先写好后填
③ f-string(Python 3.6+,推荐):
f"{x}" # 变量
f"{a + b}" # 表达式
f"{obj.attr}" # 属性/方法/下标
f"{x:.2f}" # 格式规格
最简洁:占位处直接写表达式
④ string.Template(安全场景):
from string import Template
Template("$name").substitute(name=x)
只支持简单 $var 替换,不执行代码 → 安全
用于:用户提供的模板文本
演进:% → format → f-string,越来越简洁强大
Template 是并列的"安全特化"分支
所以四种:% (老式 printf)、format(灵活模板)、f-string(最简推荐)、Template(安全)
四种方式:① % 格式化(printf 风格、最老,"%s"%x、"%.2f"%f、多个用元组,来源 C 的 printf);② str.format()(Py2.6+,"{}".format(x) 自动位置、"{0}" 索引、"{name}" 命名,灵活可复用模板);③ f-string(Py3.6+ 推荐,f"{x}"、f"{a+b}" 表达式、f"{x:.2f}",占位处直接写表达式最简洁);④ string.Template(安全场景,$name 占位、只支持简单替换不执行代码、用于用户提供的模板)。演进 % → format → f-string 越来越简洁强大,Template 是并列的「安全特化」分支。理解「四种:%(老式 printf)、format(灵活模板)、f-string(最简推荐 3.6+)、Template(安全 $var);演进 %→format→f-string 越来越简洁」,就认识了四种方式。
二、f-string 为什么推荐
理解 f-string 的三大优势:
f-string 的三大优势:
① 直观——表达式直接写在占位处
f"{user.name} 有 {len(items)} 件商品,总价 {price:.2f} 元"
→ 一眼看懂哪个变量放哪,不用对着 format 的参数数位置
对比 format:
"{} 有 {} 件,总价 {:.2f}".format(user.name, len(items), price)
→ 要在脑子里对应占位符和参数,容易错位
② 快——编译期解析成拼接
f-string 在编译时就转成等价的字符串拼接/构造操作
运行时不需要解析格式串、不需要查参数
→ 比 % 和 .format() 都快(基准测试通常快 1.5~2 倍)
③ 功能全——支持所有格式规格 + 独有特性
格式规格:f"{x:.2f}"、f"{n:,}"、f"{s:>10}"
转换:f"{x!r}"(repr)、f"{x!s}"(str)、f"{x!a}"(ascii)
调试(3.8+):f"{x=}" → 输出 "x=值"(打印变量名和值)
嵌套:f"{x:{width}.{prec}f}"(宽度/精度也用变量)
f-string 里能写什么:
变量、算术、函数调用、属性、下标、三元、推导式...
f"{'偶' if n%2==0 else '奇'}"
f"{[i**2 for i in range(3)]}"
几乎任意表达式(不能有反斜杠转义、3.12 前不能同引号嵌套)
所以 f-string 优势:直观(表达式就地写)+快(编译期拼接)+功能全(格式规格/调试)
f-string 的三大优势:① 直观——表达式直接写在占位处(f"{user.name} 有 {len(items)} 件" 一眼看懂哪个变量放哪,format 要对应占位符和参数容易错位);② 快——编译期解析成拼接(编译时转成等价拼接操作、运行时不解析格式串不查参数,比 % 和 format 快约 1.5~2 倍);③ 功能全——支持所有格式规格({x:.2f}、{n:,})+ 转换({x!r} repr、{x!s} str)+ 调试(3.8+ {x=} 输出「x=值」)+ 嵌套({x:{width}.{prec}f})。f-string 里能写几乎任意表达式(变量/算术/函数/属性/下标/三元/推导式)。理解「f-string 优势:①直观(表达式就地写不用对参数)②快(编译期拼接快 1.5-2 倍)③功能全(格式规格+!r 转换+{x=}调试+嵌套);里面能写几乎任意表达式」,就理解了为什么推荐 f-string。
三、格式规格 mini-language
理解三种方式通用的格式规格:
格式规格(format spec):冒号后面的部分 {值:格式}
三种方式(%除外,%用自己的语法)通用:
f"{x:格式}"、"{:格式}".format(x)
通用结构(按顺序,都可选):
[[fill]align][sign][#][0][width][,][.prec][type]
① 精度/小数:.n
f"{3.14159:.2f}" → 3.14(2位小数)
f"{3.14159:.0f}" → 3(整数)
② 千分位:, 或 _
f"{1234567:,}" → 1,234,567
f"{1234567:_}" → 1_234_567
③ 对齐 + 宽度:< 左 > 右 ^ 居中
f"{'hi':>10}" → " hi"(右对齐宽10)
f"{'hi':<10}" → "hi "
f"{'hi':^10}" → " hi "
f"{'hi':*^10}" → "****hi****"(用*填充)
④ 补零:0 或 类型前加0
f"{42:05d}" → "00042"
f"{3.14:08.2f}" → "00003.14"
⑤ 进制/类型:
f"{255:x}" → ff(十六进制小写)
f"{255:X}" → FF
f"{255:b}" → 11111111(二进制)
f"{255:o}" → 377(八进制)
f"{255:#x}" → 0xff(带前缀)
⑥ 百分比:%
f"{0.1234:.1%}" → 12.3%(自动×100加%)
⑦ 正负号:+
f"{5:+d}" → +5
f"{-5:+d}" → -5
⑧ 千分位+小数+对齐组合:
f"{1234.5:>12,.2f}" → " 1,234.50"
所以格式规格 {值:格式}:.2f 小数、, 千分位、>^< 对齐、05 补零、x 进制、% 百分比
格式规格(format spec) 是冒号后的部分 {值:格式},三种方式(% 除外)通用。通用结构(都可选):[[fill]align][sign][#][0][width][,][.prec][type]。① 精度/小数 .n({3.14159:.2f}→3.14);② 千分位 , 或 _({1234567:,}→1,234,567);③ 对齐+宽度 <左>右^居中({'hi':>10}、{'hi':*^10}→“hi” 用 * 填充);④ 补零 0({42:05d}→00042);⑤ 进制/类型({255:x}→ff、{255:b}→二进制、{255:#x}→0xff);⑥ 百分比 %({0.1234:.1%}→12.3%);⑦ 正负号 +({5:+d}→+5);⑧ 组合({1234.5:>12,.2f})。理解「格式规格{值:格式}:.2f 小数、,千分位、><^对齐+宽度、05 补零、x/b/o 进制、#带前缀、%百分比、+正负号,可组合」,就掌握了格式规格。
四、% 格式化——老式但仍常见
理解 % 格式化及其在哪还常用:
% 格式化(printf 风格):
"%[flags][width][.prec]type" % 值
常见转换符:
%s 字符串(对任意对象调 str())
%r repr()
%d 整数
%f 浮点(%.2f 两位)
%x 十六进制
%% 字面 %
单个 vs 多个:
"%s" % x # 单个直接
"%s %s" % (a, b) # 多个用元组
"%(name)s" % {"name": x} # 命名用字典
% 的坑:
① 单个元组值要小心:
"%s" % (1,) → "1",但 "%s" % (1,2) → 报错(太多参数)
→ 想传元组本身要 "%s" % ((1,2),)
② 类型不匹配报错:%d 传字符串 → TypeError
% 现在还在哪用(别急着淘汰):
① logging 惰性格式(重要):
log.info("user %s did %s", user, action)
→ 只有日志真要输出时才格式化(DEBUG 关闭时零开销)
→ 比 log.info(f"user {user}...") 省(f-string 总会先算)
② 遗留代码、C 风格偏好
所以 % 是老式 printf 风格,logging 惰性格式仍推荐用它(省开销)
% 格式化(printf 风格):"%[flags][width][.prec]type" % 值。常见转换符:%s(字符串、调 str)、%r(repr)、%d(整数)、%f(浮点、%.2f 两位)、%x(十六进制)、%%(字面 %)。单个直接 "%s" % x、多个用元组 "%s %s" % (a,b)、命名用字典 "%(name)s" % {...}。% 的坑:① 单个元组值要小心("%s" % (1,2) 报错、想传元组本身要 ((1,2),))、② 类型不匹配报错。% 现在还在哪用:① logging 惰性格式(重要):log.info("user %s did %s", user, action)——只有日志真要输出时才格式化(DEBUG 关闭时零开销,比 log.info(f"...") 省,因 f-string 总会先算);② 遗留代码、C 风格偏好。理解「%老式 printf 风格:%s/%r/%d/%f/%x;多个用元组、命名用字典;坑单个元组要小心;★logging 惰性格式仍推荐用%(log.info(‘%s’,x)只在要输出时才格式化省开销)」,就掌握了 % 格式化。
五、选型与安全
理解不同场景该用哪种、安全注意:
选型指南:
日常输出/拼接 → f-string(简洁快功能全)
日志 → % 惰性(log.info("%s", x))
用户提供的模板文本 → string.Template(安全)
需要模板复用/延迟填充 → str.format() 或 Template
遗留代码 → 维持 % 或按需迁移
安全注意(重要):
① 别用 f-string / format 拼 SQL:
✗ f"SELECT * FROM users WHERE name='{name}'" # SQL 注入!
✓ cursor.execute("SELECT * FROM users WHERE name=%s", (name,))
# 参数化查询(这里的 %s 是 DB 驱动的占位,不是字符串格式化)
② 别用 f-string 拼 shell 命令:
✗ os.system(f"rm {filename}") # 命令注入!
✓ subprocess.run(["rm", filename]) # 参数列表
③ str.format() 的注入风险:
用户控制格式字符串时,format 能访问对象属性:
"{x.__class__}".format(x=obj) # 泄露内部
"{0.__init__.__globals__}" # 可能泄露敏感数据
→ 用户提供模板一定用 Template(只做 $var 替换,不执行)
④ f-string 的求值时机:
f"{x}" 定义时立即求值 → 不能当"模板"存起来复用
要延迟/复用用 format 或 Template
所以选型:日常 f-string、日志%惰性、用户模板 Template;别用 f-string 拼 SQL/shell
选型指南:日常输出/拼接用 f-string、日志用 % 惰性、用户提供的模板文本用 Template(安全)、需要模板复用/延迟填充用 format 或 Template、遗留代码维持 %。安全注意:① 别用 f-string/format 拼 SQL(f"...name='{name}'" 是 SQL 注入,要用参数化查询 cursor.execute("...=%s", (name,)));② 别用 f-string 拼 shell(os.system(f"rm {filename}") 是命令注入,用 subprocess.run([...]) 参数列表);③ str.format 的注入风险(用户控制格式串时 format 能访问对象属性 "{0.__init__.__globals__}" 泄露敏感数据,用户模板一定用 Template);④ f-string 定义时立即求值(不能当模板存起来复用,要延迟用 format/Template)。理解「选型日常 f-string、日志%惰性、用户模板 Template;安全别用 f-string/format 拼 SQL(参数化查询)/shell(subprocess 列表)、format 用户控制格式串有注入风险(用 Template)、f-string 立即求值不能存复用」,就掌握了选型与安全。
六、总结与速查
总结字符串格式化:
四种方式速查:
f-string f"{x:.2f}" 日常首选(简洁快功能全,3.6+)
format "{:.2f}".format(x) 需要复用模板/延迟填充
% "%.2f" % x 日志惰性、遗留代码
Template Template("$x") 用户提供模板(安全)
f-string 独有特性:
{x=} 调试(输出 "x=值",3.8+)
{x!r} repr 转换
{x:{w}.{p}f} 嵌套(宽度精度用变量)
表达式 f"{a+b}"、f"{func()}"
格式规格通用(f-string / format 共用):
.2f 两位小数 , 千分位 % 百分比
>10 右对齐宽10 <10 左对齐 ^10 居中
05d 补零 +d 带正负号 x/b/o 进制 # 带前缀
安全红线:
别用 f-string/format 拼 SQL(参数化查询)
别用 f-string 拼 shell(subprocess 列表)
用户提供的模板用 Template(不执行代码)
核心总结:
优先 f-string(简洁、最快、功能全)
格式规格 {值:格式} 三种方式通用
日志用 % 惰性、用户模板用 Template
别拿格式化拼 SQL/shell(注入)
所以优先 f-string,格式规格{值:格式}通用,日志%惰性,安全别拼 SQL/shell
四种方式速查:f-string(f"{x:.2f}" 日常首选)、format("{:.2f}".format(x) 复用模板/延迟填充)、%("%.2f" % x 日志惰性/遗留)、Template($x 用户模板安全)。f-string 独有特性:{x=} 调试、{x!r} repr、{x:{w}.{p}f} 嵌套、表达式。格式规格通用:.2f 小数、, 千分位、% 百分比、>^< 对齐、05d 补零、+d 正负号、x/b/o 进制、# 前缀。安全红线:别用 f-string/format 拼 SQL(参数化查询)、别拼 shell(subprocess 列表)、用户模板用 Template。理解「速查 f-string 首选/format 复用/‰惰性/Template 安全;f-string 独有{x=}调试/!r/嵌套;格式规格通用;安全别拼 SQL/shell 用户模板 Template」,就掌握了总结与速查。
记忆钩子:「Python 字符串格式化四种:①%格式化(老式 printf,‘%s’%x、多个用元组,logging 惰性格式仍推荐 log.info(‘%s’,x)只在要输出时才算省开销)②str.format()(’{}‘.format(x)、‘{0}‘索引、‘{name}‘命名,可复用模板/延迟填充)③f-string(3.6+推荐 f’{x}‘,把表达式直接写进{}最简洁最快)④string.Template($name 只做简单替换不执行代码,用户提供模板用它最安全);★f-string 三大优势:直观(表达式就地写不用对参数)+快(编译期解析成拼接、比%和 format 快 1.5-2 倍)+功能全(独有{x=}调试输出’x=值’3.8+、{x!r}转 repr、{x:{w}.{p}f}嵌套);格式规格{值:格式}三种通用:.2f 小数、,千分位、%百分比、>^<对齐+宽度、05d 补零、x/b/o 进制、#前缀、+正负号;安全红线:别用 f-string/format 拼 SQL(用参数化查询)/shell(用 subprocess 列表)、用户控制的格式串用 Template(format 能访问__globals__泄露)、f-string 定义时立即求值不能当模板存」。
七、常见误区与追问
- 误区:f-string 只是语法糖,和 format 一样慢。 不一样——f-string 更快:它在编译期就被解析成等价的字符串拼接/构造操作,运行时不需要像
%和.format()那样解析格式字符串、查找和匹配参数;基准测试中 f-string 通常比.format()和%快约 1.5~2 倍;所以 f-string 既最直观又最快,是日常首选。 - 误区:所有格式化都应该换成 f-string。 不是——日志应该保留
%惰性格式:log.info("user %s did %s", user, action)只在日志级别真要输出时才格式化(DEBUG 关闭时零开销),而log.info(f"user {user}...")会无条件先算好 f-string 再传给 logging(浪费);用户提供的模板文本应该用string.Template(安全);需要「先定义模板、后填充/复用」的场景用.format()。 - 误区:用 f-string 拼 SQL 查询很方便。 极其危险——
f"SELECT * FROM users WHERE name='{name}'"是 SQL 注入漏洞(如果 name 来自用户输入,可以构造恶意值篡改查询);正确做法是用数据库驱动的参数化查询cursor.execute("... WHERE name=%s", (name,))(这里的 %s 是驱动的参数占位符、由驱动安全转义,不是字符串格式化);同理别用 f-string 拼 shell 命令(用 subprocess 的参数列表)。 - 误区:str.format() 用在任何字符串上都是安全的。 不是——如果格式字符串本身来自用户输入,
.format()会成为漏洞:因为它能访问对象的属性和方法,"{0.__class__.__init__.__globals__}".format(obj)之类的格式串能泄露全局变量、敏感数据;所以「用户能控制格式字符串」的场景绝不能用.format(),要用string.Template(只做简单的$var替换、不执行任何代码/属性访问)。 - 追问:f-string 相比 % 和 format 有哪些独有的特性? 主要有:① 表达式就地嵌入——
{}里可以直接写任意表达式(f"{a+b}"、f"{func()}"、f"{obj.attr}"、f"{'偶' if n%2==0 else '奇'}"),不用像 format 那样把值作为参数传;② 自文档化调试(3.8+)——f"{x=}"会输出x=值(同时打印变量名和值,调试神器);③ 嵌套格式规格——f"{x:{width}.{prec}f}"(宽度和精度也能用变量动态指定);④ 转换标志{x!r}(repr)、{x!s}(str)、{x!a}(ascii);这些让 f-string 既简洁又强大。 - 追问:格式规格里怎么实现「保留两位小数的千分位金额、右对齐补齐到 12 宽」? 用组合的格式规格
f"{amount:>12,.2f}":>表示右对齐、12是总宽度、,是千分位分隔、.2f是保留两位小数的浮点;比如f"{1234.5:>12,.2f}"得到" 1,234.50"(前面补空格到 12 宽);格式规格的顺序是[[fill]align][sign][#][0][width][grouping][.precision][type],各部分都可选、按这个顺序组合;这套 mini-language 在 f-string 和.format()里通用。 - 追问:为什么 logging 推荐用
log.info("x=%s", x)而不是log.info(f"x={x}")? 因为「惰性格式化」省开销:log.info("x=%s", x)把格式字符串和参数分开传给 logging,只有当这条日志真的会被输出时(日志级别允许)logging 才会去做%格式化;如果当前级别是 INFO 而这是 DEBUG 日志、或这条日志被过滤掉了,格式化根本不发生、零开销;而log.info(f"x={x}")里 f-string 在调用前就已经无条件求值拼好了字符串(不管这条日志最终会不会输出),在高频日志、复杂对象的 repr 很贵时会造成明显浪费;所以 logging 是少数仍推荐用%的场景。
八、加强记忆
Python 字符串格式化主要四种,推荐 f-string:① % 格式化(老式 printf 风格,"%s"%x、多个用元组、%.2f;logging 惰性格式仍推荐用它——log.info("%s", x) 只在日志真要输出时才格式化、省开销);② str.format()("{}".format(x)、"{0}" 索引、"{name}" 命名,可复用模板/延迟填充);③ f-string(3.6+ 推荐)(f"{x}",把表达式直接写进 {} 最简洁);④ string.Template($name 只做简单替换不执行代码,用户提供的模板用它最安全)。f-string 三大优势:直观(表达式就地写)+ 快(编译期解析成拼接、比 % 和 format 快约 1.5~2 倍)+ 功能全(独有 {x=} 调试输出「x=值」、{x!r} 转 repr、{x:{w}.{p}f} 嵌套)。格式规格 {值:格式} 三种通用:.2f 小数、, 千分位、% 百分比、>^< 对齐+宽度、05d 补零、x/b/o 进制、# 前缀、+ 正负号(可组合 {x:>12,.2f})。安全红线:别用 f-string/format 拼 SQL(用参数化查询)或 shell(用 subprocess 列表)、用户控制的格式串用 Template。一句话「优先 f-string(直观+编译期拼接最快+功能全 like {x=}调试);格式规格{值:格式}三种通用(.2f/,/%/对齐/进制);日志用%惰性(只在要输出时才算)、用户模板用 Template(安全);别拿格式化拼 SQL/shell(注入)」。