eval和exec既不安全又慢的根本原因是跳过编译期检查、运行时重复解析且共享作用域;ast.literal_eval仅支持字面量,simpleeval通过ast白名单校验更安全,多语句逻辑必须进程隔离加资源限制。

eval 和 exec 为什么既不安全又慢
根本原因在于:它们直接把字符串喂给 Python 解释器,跳过所有编译期检查,等于在运行时重新做一遍词法分析、语法解析、AST 构建、字节码生成——这本身就有开销;更关键的是,它们默认共享当前作用域,任何变量、内置函数、类型对象都可能被恶意构造的字符串触达。
常见错误现象:eval("__import__('os').system('id')") 在本地测试时看似无害,但只要用户能控制输入字符串,攻击者就能通过 __import__、getattr、type().__subclasses__() 等链式调用复活被删掉的模块,绕过 del __builtins__ 这类表面防护。
性能上,哪怕只是 eval("2 + 3"),也要走完整解析流程;而同样逻辑写成函数,CPython 会缓存字节码,下次调用直接执行。
ast.literal_eval 是唯一可信任的内置方案
它只接受字面量:数字、字符串、列表、元组、字典、布尔值、None。遇到任何运算符、变量名、函数调用都会抛 ValueError,不执行、不求值、不访问任何对象属性。
使用场景明确:解析配置文件、读取 JSON-like 用户输入、反序列化可信来源的结构化数据。
-
ast.literal_eval("[1, 'hello', {'x': True}]")→ 正常返回 -
ast.literal_eval("{'a': 1 + 2}")→ 报错(含+,非法) -
ast.literal_eval("__import__('os')")→ 报错(非字面量)
注意:它不支持任何形式的动态计算,连 "1.5 * 2" 都不行,别试图用它做表达式求值。
需要算术表达式?用 simpleeval 而不是 eval
simpleeval 是专为安全求值设计的小型库,底层基于 AST 白名单校验,不依赖 eval 或 exec,默认禁用所有函数、属性访问、下标、调用等危险操作。
实操建议:
- 只显式注入你需要的变量:
s.eval("2 * x + y", names={"x": 5, "y": 3}) - 只开放极少数函数:
s.functions["sin"] = math.sin - 它不处理超时,需外层用
concurrent.futures.ProcessPoolExecutor包裹 - 别传
globals()或locals(),否则白名单失效
错误示例:simpleeval.SimpleEval().eval("__import__('os')") 直接抛 NameError,比手写 AST Visitor 更省事也更可靠。
真要执行多语句逻辑?必须进程隔离 + 资源限制
exec 支持赋值、循环、定义函数,但这也意味着它能污染命名空间、无限循环、耗尽内存。线程级 timeout(如 threading.Timer)对 C 扩展阻塞无效,signal.alarm 在 Windows 不可用。
可行路径只有一条:用 multiprocessing.Process 启动子进程,配合 psutil.Process 或 Linux prlimit 设硬性内存/CPU 限制,并用 Queue 通信。
容易踩的坑:
- 别在子进程中直接
print或写日志——主进程看不到,建议统一走Queue返回结构化结果 - 子进程不能访问主进程的闭包变量或 lambda,所有数据必须序列化传递
-
p.terminate()后必须跟p.join(),否则僵尸进程堆积
复杂点在于:你得自己处理异常传播、超时判定、资源回收和错误上下文还原——这些都不是 eval 调用能自动解决的。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











