Python 3.14 template string 如何用于受控模板解析
来源:17golang原创
时间:2026-09-11 16:43:22 418浏览 收藏
如果你用 f-string 拼 HTML、日志或其他有语法约束的文本,插值值会直接进入最终字符串,处理器没有机会在合并前检查它。Python 3.14 的 template string(也叫 t-string)改变了这个边界:t"..." 生成的是 string.templatelib.Template,里面分别保存静态字符串和 Interpolation 对象。
t-string 只是把“合并前的结构”交给你;它不会自动转义 HTML,也不会自动阻止 SQL 注入。真正的安全性来自处理函数对字段、类型、转换和输出上下文的限制。
Template.strings保存静态片段,interpolations保存已经求值的插值。- 受控解析器应先检查
expression白名单,再按输出上下文转义值。 - 转换和
format_spec不会像 f-string 那样自动执行,处理器必须明确决定怎么处理。
先看清 t-string 和 f-string 的返回差异
f-string 的目标是马上得到 str;t-string 的目标是得到可遍历的模板结构。表达式仍会在模板出现的位置立即从左到右求值,所以不要把 t-string 当成延迟执行或沙箱。区别在于求值结果会被放进 Interpolation.value,表达式文本放进 expression,另外还保留转换标记和格式说明。
from string.templatelib import Interpolation, Template
name = "Ada"
template = t"你好,{name!s}"
assert isinstance(template, Template)
item = template.interpolations[0]
print(template.strings) # 静态片段,例如 ('你好,', '')
print(item.value, item.expression) # 动态值与表达式文本
这里的 expression 适合做审计和白名单匹配,但不要把它重新交给 eval()。t-string 已经完成了表达式求值,受控处理器只应处理拿到的值。

用字段白名单限制可处理的插值
受控模板通常不是“任何表达式都能输出”。例如邮件标题只允许 user_name 和 order_id,模板里出现函数调用、属性访问或拼错的字段就应该失败。白名单的判断对象是 Interpolation.expression;值的类型检查则放在同一处理函数内。
from string.templatelib import Interpolation, Template
ALLOWED = {"user_name": str, "order_id": int}
def render_text(template: Template) -> str:
parts: list[str] = []
for piece in template:
if isinstance(piece, str):
parts.append(piece)
continue
# 只接受完整字段名,未知表达式不进入输出层。
if not isinstance(piece, Interpolation) or piece.expression not in ALLOWED:
raise ValueError(f"不允许的模板字段: {piece.expression!r}")
expected = ALLOWED[piece.expression]
if not isinstance(piece.value, expected):
raise TypeError(f"字段 {piece.expression} 类型不匹配")
# 先按本项目约定保留原值,转换和格式化另行处理。
parts.append(str(piece.value))
return "".join(parts)
这个策略有一个容易忽略的好处:模板作者只能选择字段,不能借模板语法临时调用 os.getenv() 或对象方法。若模板来自数据库或用户输入,不能直接把它拼成 Python 源码再执行;需要解析外部模板时,应另写只支持固定占位符的解析器。
输出 HTML 时要在处理器里转义
以 HTML 文本节点为例,静态模板片段可以直接拼接,但用户昵称、订单备注这类动态值必须经过 HTML 转义。下面的处理器保留固定字段,并使用 html.escape 处理文本;它不会把动态值当作 HTML 标签执行。
from html import escape
from string.templatelib import Interpolation, Template, convert
def render_html(template: Template) -> str:
parts: list[str] = []
for piece in template:
if isinstance(piece, str):
parts.append(piece)
continue
# expression 只作字段标识;绝不重新执行表达式文本。
if piece.expression not in {"user_name", "order_id"}:
raise ValueError("模板包含未登记字段")
value = convert(piece.value, piece.conversion)
if piece.format_spec:
# 格式说明也要受项目策略控制,避免任意格式协议进入输出。
if piece.expression != "order_id" or piece.format_spec not in {"", "06d"}:
raise ValueError("当前字段不支持该格式说明")
value = format(value, piece.format_spec)
parts.append(escape(str(value), quote=True))
return "".join(parts)
user_name = "
"
order_id = 42
page = render_html(t"您好,{user_name}
订单:{order_id:06d}
")
示例中的静态 HTML 只来自程序代码,动态值始终走 escape。如果模板允许动态属性、URL 或富文本,不能沿用同一套文本转义规则,应为每种上下文定义独立的安全类型和处理分支。

迁移时把转换、格式化和 SQL 边界分开
t-string 的 !s、!r、!a 会记录在 conversion,但不会像 f-string 一样由语言自动把值转换成最终文本;format_spec 也交给处理器决定。标准库的 string.templatelib.convert() 可以复用 f-string 的转换语义,但是否允许某个格式说明,仍应由应用层控制。
这也是迁移旧代码时最容易误判的地方:把 f"SELECT ... {user_id}" 改成 t"SELECT ... {user_id}",再调用一个通用拼接函数,并不会自动获得 SQL 安全。SQL 仍应使用驱动提供的参数绑定;t-string 更适合需要在合并前构造中间表示、转义 HTML、生成结构化日志或领域专用文本的场景。
| 场景 | 建议 | 原因 |
|---|---|---|
| 普通显示文本 | 继续用 f-string | 直接得到 str,处理链最简单 |
| HTML 文本模板 | t-string + 上下文转义 | 可在插值合并前拦截动态值 |
| SQL 查询条件 | 驱动参数绑定 | 不要把 t-string 当作 SQL 安全层 |
| 结构化日志 | 读取 expression 和 value | 同一模板可生成文本和字段数据 |
上线前至少检查四件事:运行时确实是 Python 3.14;未知字段会失败;包含尖括号和引号的动态文本会被正确转义;每一种允许的格式说明都有测试。若应用还支持 Python 3.13 或更早版本,t-string 语法本身就无法被旧解释器解析,应先做版本分流或继续使用兼容实现。
常见问题与边界
t-string 会自动防止 XSS 吗?
不会。它只保留插值结构,必须由 HTML 处理器按文本、属性或 URL 上下文分别转义。
能不能用 expression 调用 eval 重新计算?
不应该。表达式已经在 t-string 创建时求值;重新执行会扩大权限边界,也破坏白名单策略。
没有插值的 t-string 有什么不同?
它仍是 Template,不是普通 str。如果只需要固定字符串,普通字符串更直接。
迁移检查清单
先确认使用场景确实需要“合并前处理”,再把允许字段、类型、转换和格式说明写成明确规则。最后为恶意文本、未知表达式、旧版本解释器和错误格式分别保留回归用例。这样 t-string 才是可控的模板入口,而不是换了前缀的字符串拼接。
-
Golang · Go教程 | 2星期前 | 标准库 · golang · 测试 · go · 迭代器 · 字符串处理 · Go 迭代器 换行 range over function strings.Lines470 收藏
-
Golang · Go教程 | 2星期前 | 标准库 · 性能优化 · 字符串处理 · Go教程 · 内存分配 strings.Split Go 1.24 Go 迭代器 Go strings.SplitSeq363 收藏
-
184 收藏
-
164 收藏
-
Golang · Go教程 | 2星期前 | 标准库 · 性能 · 迭代器 · 字符串处理 · Go教程 · Go Go 1.24 iter.Seq strings.SplitAfterSeq 字符串分段141 收藏
-
480 收藏
-
391 收藏
-
213 收藏
-
254 收藏
-
428 收藏
-
484 收藏
-
230 收藏
-
文章 · python教程 | 23小时前 | decimal · Python教程 · 金额处理 · 精确计算 · 数据舍入 · Python decimal 舍入模式 quantize ROUND_HALF_UP ROUND_HALF_EVEN306 收藏
-
143 收藏
-
449 收藏
-
文章 · python教程 | 1天前 | python · glob · pathlib · 文件路径 · 递归搜索 · Python pathlib Path.rglob 隐藏目录 Path.glob 递归匹配207 收藏
-
193 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习