应避免使用 eval(),因其可执行任意代码;推荐用 ast.literal_eval() 处理字面量,或 simpleeval 库进行可控算术计算,必要时自定义 AST 解析器并严格限制节点类型、数值范围与执行时间。

为什么不能直接用 eval()
用户传入的字符串如果含 __import__、open()、exec() 或循环耗尽内存的表达式,eval() 会直接执行——这不是计算,是执行任意代码。哪怕加了 globals={"__builtins__": {}},仍可能通过 ().__class__.__mro__[1].__subclasses__() 绕过限制,实际生产环境已有多起被利用案例。
用 ast.literal_eval() 处理安全子集
它只允许字面量:数字、字符串、元组、列表、字典、布尔值和 None,不支持算术运算或变量引用。适合解析配置、JSON-like 输入,但无法算 "2 + 3 * 4"。
常见错误现象:ValueError: malformed node or string —— 比如传入了带函数调用或比较操作符的字符串。
- 能安全解析:
ast.literal_eval("[1, 'hello', {'a': 2}]") - 会报错:
ast.literal_eval("2 + 3")、ast.literal_eval("__import__('os')") - 注意:它不处理 Unicode 转义以外的编码,比如
"\x61"可以,但"\u0061"在旧 Python 版本中可能失败
用 simpleeval 库做可控算术计算
这是目前最轻量且维护活跃的方案,白名单机制明确,默认只开放基础运算符、内置函数(abs、round 等)和常量(True、False、None),不暴露任何对象属性或方法链。
立即学习“Python免费学习笔记(深入)”;
安装:pip install simpleeval
实操建议:
- 禁用所有函数:
se = SimpleEval(functions={}) - 限制最大嵌套深度(防栈溢出):
se.nodes = {ast.Expression, ast.BinOp, ast.UnaryOp, ast.Num, ast.Str, ast.NameConstant} - 设超时(需配合信号或线程):
se.time_limit = 0.1(注意:仅对部分操作生效,复杂表达式仍可能卡住) - 避免传入用户可控的
names字典,除非你完全信任其键名和值类型
示例:SimpleEval().eval("2 ** 10 + len('abc')") → 1027;而 "[x for x in range(1000000)]" 会被拒绝(列表推导式不在默认白名单)
自定义 AST 遍历器的边界在哪
如果你需要支持自定义函数(如 sin(x))、变量代入或单位转换,又不愿引入第三方依赖,就得手写 ast.NodeVisitor。但必须意识到:这不再是“简单计算”,而是构建一个微型领域语言解释器。
容易踩的坑:
- 漏判
ast.Call中的func是ast.Attribute(如math.sin)导致绕过函数白名单 - 没限制
ast.Constant的数值范围,导致10**1000000触发内存爆炸 - 忽略
ast.BoolOp和ast.Compare的短路行为,用递归求值时引发非预期副作用 - Python 3.8+ 的
ast.Constant替代了旧版的ast.Num/ast.Str,兼容性要显式处理
真正安全的动态计算,从来不是“怎么让它跑起来”,而是“怎么让它只能做你画好的那几件事”。越靠近业务逻辑(比如财务公式、IoT 设备阈值表达式),越要收窄语法树节点类型、数值范围和执行时间,而不是堆砌防御层。


















