登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  python教程

Python LiteralString 怎样约束拼接后的字符串

来源:17golang原创

时间:2026-10-18 14:10:47 210浏览 收藏

我第一次把动态输入拼进敏感字符串时,最容易犯的错不是语法写错,而是把“看起来只是一个 str”的值交给了不该接收任意文本的接口。Python 的 LiteralString 可以把这条边界写进函数签名:字面量和由字面量组成的字符串可以通过,来源不明的普通 str 会被静态类型检查器拦下。

官方地址:https://docs.python.org/3/library/typing.html#typing.LiteralString

核心做法是:让 SQL、Shell 模板或其他敏感 API 只接收 LiteralString,把用户输入放到独立的参数位。LiteralString 只负责静态约束,运行时仍然是普通字符串,不能替代参数化查询、转义和权限控制。

LiteralString 与普通 str 的边界在哪里

LiteralString 在 Python 3.11 加入 typing,它不是运行时的新类,而是给静态类型检查器识别的特殊类型。可以把它理解成“已知由字符串字面量构成”的范围:字面量可以传入,另一个 LiteralString 也可以传入;一个只标注为 str 的变量则不能直接传入。

from typing import LiteralString

def execute_safe(statement: LiteralString) -> None:
    # 敏感 API 只接受可追溯到字面量的模板。
    print(statement)

literal_sql = "SELECT id FROM users"
dynamic_sql: str = "SELECT id FROM " + input("table: ")

execute_safe(literal_sql)   # 通过:来源是字符串字面量
execute_safe(dynamic_sql)   # 类型检查器应报告:普通 str 不是 LiteralString

这里的“拒绝”发生在检查阶段,不是 Python 运行时抛出的异常。PEP 675 把 LiteralString 放在具体字面量类型和普通 str 之间:它可以赋值给 str,但普通 str 不能反向赋值给 LiteralString。

字符串字面量、LiteralString、普通 str 与敏感 API 的静态类型边界说明图
图1:LiteralString 与普通 str 的类型边界说明图;这是静态结构图,不是运行截图。

哪些拼接仍然保留 LiteralString

标题里的“拼接”不是一律不允许。只要参与组合的值都属于字面量字符串或 LiteralString,类型检查器可以继续把结果视为 LiteralString。普通 str 一旦进入组合,结果就会退回普通 str。

from typing import LiteralString

def run_query(query: LiteralString) -> None:
    # 这里只展示类型边界;实际数据库调用仍应使用参数化接口。
    print(query)

base: LiteralString = "SELECT id FROM users"
suffix: LiteralString = " WHERE active = 1"
safe_query = base + suffix
run_query(safe_query)  # 通过:两部分都在 LiteralString 边界内

user_name: str = input("name: ")
unsafe_query = base + " WHERE name = '" + user_name + "'"
run_query(unsafe_query)  # 应被静态检查器拒绝

columns = ["id", "email"]
projection = ", ".join(columns)
run_query(projection)  # 具体结果取决于检查器对容器元素的推断

同样的规则适用于字面量分隔符的 join,以及只使用字面量字符串组成的格式化表达式。不要把“检查器接受了拼接”误读成“SQL 已经安全”:它只说明字符串来源边界满足类型契约。

动态数据应该留在参数位

更稳妥的工程习惯是把模板和数据分成两个参数。模板由代码维护,用户输入作为绑定值交给数据库驱动;这样既保留 LiteralString 的静态约束,也让真正执行查询的 API 处理数据转义。

from typing import LiteralString

def execute_query(query: LiteralString, params: tuple[object, ...]) -> None:
    # 模板和数据分离;真实项目中把它转交给支持参数绑定的驱动。
    print(query, params)

query: LiteralString = (
    "SELECT id, email FROM users WHERE name = ?"
)
name: str = input("name: ")
execute_query(query, (name,))  # 动态值留在参数位

# 不要把 name 拼回 query;那会把结果降级为普通 str。
bad_query = "SELECT id FROM users WHERE name = '" + name + "'"
# execute_query(bad_query, ())  # 类型检查器应提示边界被破坏

这类签名的保护资产是“代码模板和外部数据的分离”。如果敏感 API 还需要允许列名、排序方向等动态结构,就不要把它们直接拼进去,而是先用白名单把外部值映射到有限的字面量集合,再把映射后的结果交给检查器可识别的接口。

查询模板、参数化数据、用户输入和类型检查器之间的安全边界说明图
图2:敏感字符串与动态数据分离的防护边界说明图;这是静态结构图,不是执行结果截图。

Any、cast 和忽略诊断会怎样破坏边界

LiteralString 适合防止“善意代码误用敏感 API”,但它不是对抗恶意开发者的运行时防火墙。使用 Any、cast(LiteralString, value) 或忽略类型错误,都可能让普通字符串伪装成满足契约的值。

from typing import Any, LiteralString, cast

def execute_safe(statement: LiteralString) -> None:
    # 运行时无法区分 LiteralString 与 str,因此仍需真实安全控制。
    print(statement)

raw: str = input("statement: ")
untyped: Any = raw
lied = cast(LiteralString, raw)

execute_safe(untyped)  # Any 可能绕过静态检查
execute_safe(lied)     # cast 只是告诉检查器“相信我”

因此,审计时要同时看三层:敏感 API 的参数类型是否收窄到 LiteralString;动态值是否进入参数化接口;运行时是否仍有驱动、解析器、白名单或权限层的防护。没有类型检查器、没有注解,或把关键变量标成 Any 时,第一层就不会产生告警。

落地时的检查清单

  • 模板参数是否标注为 LiteralString,而不是宽泛的 str?
  • 用户输入是否保持在参数位,是否避免进入 SQL、Shell 或 HTML 模板?
  • 拼接链路中是否混入普通 str、Any 或不必要的 cast?
  • 运行时是否仍使用参数化查询、转义、白名单和最小权限?

我对 LiteralString 的实际判断是:它最有价值的地方不是替你做完安全工作,而是把“这个字符串不应来自任意外部文本”变成可审查的函数契约。只要把动态数据留在参数位,再用类型检查和运行时控制共同兜底,拼接后的字符串边界就会清楚很多。

相关问题

LiteralString 是运行时类型吗?不是。它只参与静态类型检查,运行时值仍然是普通 str。

两个 LiteralString 可以拼接吗?可以。字面量与 LiteralString 组合时,检查器可以保留这个兼容性;混入普通 str 后通常会退回普通 str。

用了 LiteralString 就不用参数化查询了吗?不用。LiteralString 是开发期的来源约束,参数化查询和运行时校验仍然是执行层的安全措施。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>