先看两行代码:
name = "<b>root</b>"
a = f"用户:{name}"
b = t"用户:{name}"
写法只差一个字母,结果完全不是一类东西。
a 是 str。代码执行到这里,name 已经被转成文本,和前面的“用户”拼死了。
b 返回的是 string.templatelib.Template。固定文本和花括号里的动态值仍然分开保存。Python 3.14 给 t-string 的定位也很明确:它是自定义字符串处理的入口,不是又一种字符串拼接语法。
跑一段看看:
order_id = 1907
amount = 318.6
message = t"订单={order_id!r},金额={amount:.2f}"
print(type(message))
print(message.strings)
print(message.values)
print(message.interpolations)
Template 里能直接拿到 strings、values 和 interpolations。
每个 Interpolation 还留着四样东西:表达式计算后的值、原始表达式文本、!r 这种转换标记,以及 .2f 这样的格式说明。转换和格式化不会自动执行,后面的处理器愿不愿意认、怎么处理,由它自己决定。
这地方才是 t-string 和 f-string 真正的分界。
f-string 一旦生成,接收方拿到的只是一整段文本。哪部分是代码写死的,哪部分来自外部输入,边界已经没了。
t-string 没急着把它们揉在一起。后面的处理器还能检查、转义、脱敏,甚至直接拒绝某类值。
比如页面输出昵称,下面这种代码我一般不会直接放过去:
html = f"<p>{nickname}</p>"
不是 f-string 自带漏洞,而是变量太早混进 HTML,后面想统一兜底已经晚了。
换成 t-string,可以加一个很薄的处理器:
from html import escape
from string.templatelib import Interpolation, Template, convert
defrender_html(template: Template) -> str:
result: list[str] = []
for part in template:
if isinstance(part, str):
result.append(part)
continue
assert isinstance(part, Interpolation)
value = convert(part.value, part.conversion)
if part.format_spec:
value = format(value, part.format_spec)
result.append(escape(str(value), quote=True))
return"".join(result)
nickname = "<img src=x onerror=alert(1)>"
page = render_html(
t"<p class='nickname'>{nickname}</p>"
)
print(page)
固定的 HTML 原样保留,动态插入的 nickname 统一转义。
以后要加字段白名单、日志脱敏或者类型限制,也都压在 render_html() 这一层,不用满项目搜索哪里偷偷拼了字符串。
不过这里别理解过头。
t-string 本身不会自动防 SQL 注入,也不会自动保证 HTML、Shell 命令安全。它只是把静态文本和动态数据的边界保留下来,让处理器有机会做正确的事。处理器什么都不检查,照样出问题。
还有一个容易理解错的地方:t-string 延迟的是最终渲染,不是表达式执行。
template = t"库存={load_stock(sku)}"
创建 template 时,load_stock(sku) 已经调用了。Interpolation.value 保存的是表达式计算后的结果,不是一个等着以后执行的函数。
所以别拿它当懒加载。变量之后发生变化,模板里的值也不会自动重新计算。
那 f-string 会不会被替代?
不会。
调试输出、异常信息、内部字符串拼接,f-string 还是更直接。为了拼一条确定的文本,硬套一层 Template 和渲染器,只会给自己增加工作量。
t-string 更适合 HTML 处理、结构化日志、SQL 构造器、命令封装和业务 DSL。也就是后续组件必须知道“哪段是模板,哪段是数据”的场景。Python 官方列出的方向也基本集中在这些地方。
f-string 是当场把结果做出来。
t-string 是先把现场留下,再决定怎么处理。
差别不在 f 和 t,在那条数据边界还在不在。