t
,称为模板字符串(Template Strings),即 t-string。name = "World"
# f-string 语法
formatted = f"Hello {name}!"
print(type(formatted)) # 输出:<class 'str'>
print(formatted) # 输出:Hello World!
# t-string 语法
templated = t"Hello {name}!"
print(type(templated)) # 输出:<class 'string.templatelib.Template'>
print(templated.strings) # 输出:('Hello ', '')
print(templated.interpolations[0].value) # 输出:World
print("".join(
item if isinstance(item, str) else str(item.value)
for item in templated
)) # 输出:Hello World!
Template
(来自 Python 3.14 新增的标准库模块 string.templatelib
)。# 使用 f-string 的不安全示例(SQL 注入风险)
sql_query = f"SELECT * FROM users WHERE name = '{user_input}'"
# 使用 f-string 的不安全示例(XSS 风险)
html_content = f"<div>{user_input}</div>"
# 使用 t-string 的安全示例
evil = "<script>alert('evil')</script>"
template = t"<p>{evil}</p>"
# 可以定义处理函数来转义内容
assert html(template) == "<p><script>alert('evil')</script></p>"
from string.templatelib import Template, Interpolation
data = {"name": "Python猫", "age": 18}
template = t"用户 {data['name']} 的年龄是 {data['age']}"
def standard_renderer(template: Template) -> str:
"""标准文本渲染"""
return "".join(
item if isinstance(item, str) else str(item.value)
for item in template
)
def json_renderer(template: Template) -> str:
"""JSON格式渲染"""
import json, re
values = {}
for item in template:
if not isinstance(item, str):
# 使用正则表达式从表达式中提取键名
# 匹配 data['name'] 或 data["name"] 模式中的name
match = re.search(r"\['([^']+)'\]|\[\"([^\"]+)\"\]", item.expression)
if match:
# 获取匹配的第一个分组
key = match.group(1) if match.group(1) else match.group(2)
values[key] = item.value
return json.dumps(values, ensure_ascii=False)
def xml_renderer(template: Template) -> str:
"""XML格式渲染"""
parts = ["<data>"]
for item in template:
if not isinstance(item, str) and hasattr(item, "expression"):
name = item.expression.split("'")[1] if "'" in item.expression else item.expression
parts.append(f" <{name}>{item.value}</{name}>")
parts.append("</data>")
return "\n".join(parts)
# 同一个模板,不同的输出格式
print(standard_renderer(template)) # 输出: 用户 Python猫 的年龄是 18
print(json_renderer(template)) # 输出: {"name": "Python猫", "age": 18}
print(xml_renderer(template)) # 输出: <data>\n <name>Python猫</name>\n <age>18</age>\n</data>
Template
类具有以下主要属性和方法:class Template:
strings: tuple[str, ...]
"""
模板中字符串部分的非空元组。
包含 N+1 个元素,其中 N 是模板中插值表达式的数量。
"""
interpolations: tuple[Interpolation, ...]
"""
模板中插值部分的元组。
如果没有插值表达式,这将是一个空元组。
"""
def __new__(cls, *args: str | Interpolation):
"""
创建一个新的 Template 实例。
参数可以按任意顺序提供。
"""
...
@property
def values(self) -> tuple[object, ...]:
"""
返回模板中每个 Interpolation 的 `value` 属性组成的元组。
如果没有插值表达式,这将是一个空元组。
"""
...
def __iter__(self) -> Iterator[str | Interpolation]:
"""
迭代模板中的字符串部分和插值表达式。
这些可能以任意顺序出现。不包含空字符串。
"""
...
访问原始字符串片段(strings
)
访问插值表达式及其计算结果(interpolations
)
直接获取所有插值的值(values
)
按顺序迭代模板的所有组成部分
__iter__
函数注释说出现顺序不固定,但 PEP 文档中它的具体实现却是按序的,我认为是注释有误。{}
作为插值表达式的分隔符.2f
)和转换说明符(如 !r
)'
、"
、'''
、"""
)t
和 T
都是有效的,就像 f
和 F
str
类型,而 t-string 返回 Template
类型def html(template: Template) -> str:
parts = []
for item in template:
if isinstance(item, str):
parts.append(item)
else: # Interpolation
parts.append(html_escape(item.value))
return "".join(parts)
user_input = "<script>alert('XSS')</script>"
safe_html = html(t"<div>{user_input}</div>")
# 输出: <div><script>alert('XSS')</script></div>
def safe_sql(template: Template) -> str:
parts = []
params = []
for item in template:
if isinstance(item, str):
parts.append(item)
else:
parts.append("?")
params.append(item.value)
return "".join(parts), params
user_id = "user' OR 1=1--"
query, params = safe_sql(t"SELECT * FROM users WHERE id = {user_id}")
# query: "SELECT * FROM users WHERE id = ?"
# params: ["user' OR 1=1--"]
import json
import logging
from string.templatelib import Template, Interpolation
class TemplateMessage:
def __init__(self, template: Template) -> None:
self.template = template
@property
def message(self) -> str:
# 格式化为可读消息
return f(self.template) # 使用自定义 f() 函数
@property
def values(self) -> dict:
# 提取结构化数据
return {
item.expression: item.value
for item in self.template.interpolations
}
def __str__(self) -> str:
return f"{self.message} >>> {json.dumps(self.values)}"
action, amount, item = "traded", 42, "shrubs"
logging.info(TemplateMessage(t"User {action}: {amount:.2f} {item}"))
# 输出: User traded: 42.00 shrubs >>> {"action": "traded", "amount": 42, "item": "shrubs"}
subprocess
模块能原生支持 t-string:# 不安全的 f-string 用法
subprocess.run(f"echo {message_from_user}", shell=True) # 命令注入风险
# 安全的 t-string 用法
subprocess.run(t"echo {message_from_user}") # 自动进行适当的命令转义
from string.templatelib import Template, Interpolation
import html
def smart_renderer(template: Template, context="text") -> str:
"""上下文感知的渲染器
根据context参数自动决定如何处理每个插值:
- "text": 普通文本模式,直接转为字符串
- "html": HTML模式,自动转义HTML特殊字符,防止XSS
- "sql": SQL模式,自动转义SQL特殊字符,防止注入
"""
parts = []
for item in template:
if isinstance(item, str):
parts.append(item)
else: # Interpolation
value = item.value
expression = item.expression
# 基于值类型和上下文进行智能处理
if context == "html":
# HTML模式:自动转义HTML特殊字符
parts.append(html.escape(str(value)))
elif context == "sql":
# SQL模式:防止SQL注入
if isinstance(value, str):
# 将1个单引号转义成2个
escaped_value = value.replace("'", "''")
parts.append(f"'{escaped_value}'")
elif value is None:
parts.append("NULL")
else:
parts.append(str(value))
else:
parts.append(str(value))
return "".join(parts)
# 同一个模板在不同上下文中的自动适配渲染
user_input = "<script>alert('evil')</script>"
template = t"用户输入: {user_input}"
print(smart_renderer(template, context="html")) # 输出: 用户输入: <script>alert('evil')</script>
# SQL注入防护示例
user_id = "1'; DROP TABLE users; --"
sql_template = t"SELECT * FROM users WHERE id = {user_id}"
print(smart_renderer(sql_template, context="sql")) # 输出: SELECT * FROM users WHERE id = '1''; DROP TABLE users; --'
# f-string 对于SQL注入,必须先处理值,再放入f-string
escaped_id = user_id.replace("'", "''")
sql_safe_id = f"'{escaped_id}'"
print(f"SQL查询(f-string): SELECT * FROM users WHERE id = {sql_safe_id}")
# f-string的嵌套:内部表达式立即求值,信息丢失
value = "world"
inner_f = f"inner {value}"
outer_f = f"outer {inner_f}"
print(outer_f) # 输出: outer inner world
print(type(outer_f)) # <class 'str'> - 只是普通字符串
# t-string的嵌套:保留完整结构信息
inner_t = t"inner {value}"
outer_t = t"outer {inner_t}"
print(type(outer_t)) # <class 'string.templatelib.Template'>
print(type(outer_t.interpolations[0].value)) # 也是Template对象!
# 可以访问和处理任意深度的嵌套结构
user = {"name": "Alice", "age": 30}
message = t"用户{user['name']}信息: {t'年龄:{user['age']}'}"
inner_template = message.interpolations[1].value
print(inner_template.strings) # 输出: ('年龄:', '')
print(inner_template.interpolations[0].value) # 输出: 30
import asyncio
# 模拟异步数据获取
async def fetch_data(key: str) -> str:
await asyncio.sleep(0.1) # 模拟网络延迟
return f"获取的{key}数据"
async def render_async(template):
tasks = {}
# 并行启动所有异步查询
for item in template.interpolations:
tasks[item.expression] = asyncio.create_task(
fetch_data(item.expression)
)
# 等待所有查询完成
for expr, task in tasks.items():
tasks[expr] = await task
# 组装结果
result = []
for item in template:
if isinstance(item, str):
result.append(item)
else:
result.append(tasks[item.expression])
return "".join(result)
async def main():
template = t"用户: {user}, 年龄: {age}"
result = await render_async(template)
print(result)
# asyncio.run(main())
Template
对象,开发者可以在字符串组合前对插值进行拦截和转换,从而避免常见的安全问题,并支持更多高级用例。