Python literal_eval 解析边界:能读出列表,为什么算不出一加二
先确定你需要读取哪种文本
一份内部小配置写着括号、列表和 None,看起来像 Python 对象。把它读回数据时,ast.literal_eval 可以识别一定范围的字面量与容器结构,但它不是一个通用表达式计算器。名字里带 eval 容易让人以为连变量、运算和函数调用都会工作,实际边界需要用具体样本确认。
下面用 Python 3.12.14 做纯内存实验。输入都是自己写的小字符串,不读取外来文件,也不制造深层嵌套或大规模压力样本。将代码保存为 demo.py,执行 python demo.py。目标是区分哪些结构能被解析、哪些表达式会被拒绝,以及解析前的大小检查究竟负责什么。
让三个阶段各自承担责任
read_small 先要求输入是字符串,再限制已解码文本的字符数,最后调用 literal_eval。这个一百二十八字符的门槛只是本例的业务限制,不是 Python 官方给出的安全阈值。样本字典含布尔值、元组和空值,解析后还要继续核对字段集合及各字段的实际类型。
随后分别传入一加二、变量名和普通函数调用。我们特意在当前作用域定义 value,再验证解析器不会去查找这个变量。失败应作为明确结果保留下来,而不是一失败就改用 eval 兜底,否则原本仅需读数据的入口就会拥有完全不同的行为。
AI概念示意图:简单数据形状进入匹配托盘,代表可执行操作的齿轮留在入口之外。图片只说明概念,不是运行截图。
import ast
def read_small(text):
if not isinstance(text, str):
raise TypeError("text required")
if len(text) > 128:
raise ValueError("input too long")
return ast.literal_eval(text)
value = 99
data = read_small("{'enabled': True, 'items': (1, 2), 'note': None}")
assert set(data) == {"enabled", "items", "note"}
assert data["enabled"] is True
assert type(data["items"]) is tuple
assert data["items"] == (1, 2)
assert data["note"] is None
print("literal:", data)
for text in ["1 + 2", "value", "dict(a=1)"]:
try:
read_small(text)
except ValueError:
print("rejected:", text)
else:
raise AssertionError("expression should be rejected")
try:
read_small(" " * 129)
except ValueError as exc:
assert str(exc) == "input too long"
print("limit:", str(exc))
else:
raise AssertionError("size guard should reject before parsing")把接受与拒绝都写进验收
第一行会输出包含 enabled、items、note 的字典,items 保持元组,布尔值和空值分别为 True 与 None。接下来三行依次显示 rejected: 1 + 2、rejected: value、rejected: dict(a=1)。这说明本例中的运算、名称读取与普通调用都未执行,不能根据 Python 能运行某段表达式就推断解析器也接受它。
最后一行 limit: input too long 来自我们自己的前置检查。这个样本只有空格,目的就是验证大小门槛在解析之前工作;它没有测试解释器资源极限。需要注意,空集合的 set() 等少量受支持形式有专门规则,不能反过来把普通函数调用的失败概括成所有带括号的文本都会失败。
解析出来的元组也提醒我们,能读出对象与满足接口契约是两回事。如果调用方只接受列表,仍要单独校验;字段缺失、未知字段和取值范围也一样。把这层验收放在解析之后,错误信息才能准确区分“文本结构不支持”与“数据内容不符合要求”。
资源限制不能靠名称来保证
官方明确提醒,literal_eval 不执行通用 Python 代码,并不等于可以放心处理任意不可信输入。复杂输入仍可能耗尽内存、栈或大量处理时间,长度检查也不是完整沙箱。外部输入还应在接收阶段限制字节量,并按服务需要设计解析资源隔离;这里不通过危险样本证明系统扛得住。
如果数据交换约定本来就是 JSON,应使用相应解析器并按 JSON 的类型规则设计字段,不要因为两种格式看起来相似就互相替换。本例只适合说明受控、小规模 Python 字面量的读取。把允许格式、规模门槛和解析后的结构校验同时写清楚,才能让后续维护者知道这条入口实际承诺了什么。


