这段代码我第一眼就不太信:
keyword = request.args["keyword"]
sql = f"SELECT id, title FROM article WHERE title = '{keyword}'"
不是 f-string 语法有问题,而是它动作太快了。
变量一塞进去,静态文本和动态数据立刻焊成一个普通字符串。等 SQL 执行器拿到它时,已经分不清哪部分是开发者写的,哪部分是用户传进来的。
Python 3.14 加入的 t-string,就是冲着这个问题来的。
顺手纠正一下时间:Python 3.14.0 已于 2025 年 10 月 7 日正式发布,t-string 不是“准备要来”,而是已经进入稳定版本。
写法只差一个字母:
order_no = "SO-20260710"
amount = 128.5
message_f = f"订单 {order_no},金额 {amount:.2f}"
message_t = t"订单 {order_no},金额 {amount:.2f}"
print(type(message_f))
print(type(message_t))
输出类型完全不同:
<class 'str'>
<class 'string.templatelib.Template'>
f-string 直接生成最终字符串。
t-string 返回的是一个 Template 对象,里面保存着静态文本、表达式结果、表达式原文、转换标记和格式规则。Python 官方把这个能力放在了 string.templatelib 模块中。
把里面的东西拆开看更直观:
price = 128.5
tpl = t"结算金额:{price:.2f} 元"
print(tpl.strings)
print(tpl.values)
slot = tpl.interpolations[0]
print(slot.value)
print(slot.expression)
print(slot.format_spec)
大概会看到:
('结算金额:', ' 元')
(128.5,)
128.5
price
.2f
这里有个地方很容易理解错:t-string 不是“延迟执行表达式”。
price 仍然会在创建模板时立即求值,而且按照从左到右的顺序执行。它延迟的只是“怎么把这些值处理成最终结果”。
这个区别很关键。
f-string 遇到 !r、!s 或者 .2f,会直接执行转换和格式化。t-string 只把这些要求记录下来,真正要不要执行、怎么执行,由后面的处理函数决定。
比如生成 HTML,我一般不愿意让业务代码到处手动调用 escape()。漏掉一次,页面上就可能混进未经处理的内容。
可以给 t-string 配一个统一渲染器:
from html import escape
from string.templatelib import Interpolation, Template, convert
defrender_html(template: Template) -> str:
fragments: list[str] = []
for part in template:
if isinstance(part, str):
fragments.append(part)
continue
ifnot isinstance(part, Interpolation):
raise TypeError(f"无法处理模板节点:{type(part)!r}")
value = convert(part.value, part.conversion)
if part.format_spec:
value = format(value, part.format_spec)
fragments.append(escape(str(value), quote=True))
return"".join(fragments)
nickname = "<b>admin</b>"
balance = 96.8
page = render_html(
t"<p>用户:{nickname}</p><p>余额:{balance:.2f}</p>"
)
print(page)
得到的结果是:
<p>用户:<b>admin</b></p><p>余额:96.80</p>
静态 HTML 标签原样保留,插值部分统一转义。业务代码只负责写模板,安全策略集中在渲染器里,不用指望每个开发者都记得处理。
但别把 t-string 当成安全开关。
它本身不会自动防 SQL 注入,也不会自动处理 Shell 参数和 HTML。它只是没有急着把数据压成普通字符串,给后续处理器留下了检查、拒绝、转义和结构化存储的机会。
SQL 还是应该优先使用参数化查询:
cursor.execute(
"SELECT id, title FROM article WHERE title = ?",
(keyword,),
)
别为了体验新语法,又把成熟的数据库参数绑定扔了。这个坑没必要踩第二遍。
普通日志、调试输出、简单提示语,继续用 f-string,直接、便宜、没什么可纠结的:
logger.info(f"任务 {task_id} 执行完成,耗时 {cost_ms}ms")
需要在插值完成前检查数据,或者要做 HTML 转义、结构化日志、命令参数校验、轻量 DSL 时,t-string 才开始有价值。
所以它不是来替换 f-string 的。
f-string 负责把字符串赶紧拼出来,t-string 则把模板拆开交给你:这一段是代码写的,那一段是外部数据,怎么处理,你自己定。这个控制权,才是 Python 3.14 真正加进来的东西。