eval不能执行赋值语句,因赋值是语句而非表达式;应改用exec,但需警惕其安全风险,推荐ast.literal_eval、白名单ast过滤或函数映射等更安全方案。

eval 为什么不能直接执行赋值语句
eval() 只能求值表达式,比如 "2 + 3"、"len('hello')"、"x > 5",它返回一个结果。一旦你传入 "x = 10" 或 "print('hi')" 这类语句,就会报 SyntaxError: invalid syntax —— 因为赋值和函数调用不是表达式。
常见误用场景:想用 eval() 动态设置变量,结果抛错。这时候该换 exec(),但它带来更严重的安全问题。
-
eval("x = 1")→ 报错;正确写法是exec("x = 1") -
eval("{'a': 1}['a']")✅ 返回1;exec("{'a': 1}['a']")❌ 不报错但无返回、无副作用 - 两者都默认在当前作用域运行,
exec()可修改局部变量(如函数内),但受locals()只读限制,实际常需显式传入globals和locals字典
exec 的沙箱隔离到底靠不靠谱
很多人以为“禁掉 __import__ 就安全了”,其实远远不够。攻击者能通过 ().__class__.__mro__[1].__subclasses__() 拿到所有子类,再从中找到 os、subprocess、open 等危险类,绕过白名单。
真正可用的隔离方式只有两种:进程级隔离(如 subprocess.run(['python', '-c', code])),或使用 restrictedpython 这类专用库(它重写 AST 并拦截危险节点)。
- 简单删掉
builtins里的open、exec、compile?没用,getattr+__builtins__仍可恢复 - 用空字典初始化
exec(code, {}, {})?危险类仍可能从类型对象链中爬出 - Python 3.12+ 的
ast.literal_eval()只支持字面量(dict/list/数字/字符串等),最安全,但完全不能执行逻辑
哪些场景真的需要动态执行,又该怎么收口
配置驱动的计算规则(如风控策略表达式)、低代码表单中的公式字段、调试时临时跑一行逻辑——这些是少数合理场景。但必须满足:输入可控、上下文极简、无外部副作用。
推荐分层处理:先用 ast.parse() 做语法检查,再遍历 AST 节点白名单过滤(只允许 BinOp、Num、Name、Compare 等),最后用 eval() 执行。比裸用 eval 多一层防御。
- 允许:
"user.age > 18 and user.city in ['Beijing', 'Shanghai']" - 拒绝:
"__import__('os').system('rm -rf /')"(AST 解析阶段就抛ValueError) - 注意
Name节点需严格校验是否在预设变量白名单中(如只允许user、now),避免注入任意变量名
替代方案比想象中更实用
90% 的所谓“动态执行需求”,其实用函数注册 + 字符串映射就能解决。比如把 "add" 映射到 lambda a,b: a+b,把 "is_adult" 映射到 lambda age: age >= 18,再用 json.loads() 解析参数,组合调用。
这样既避免代码注入,又保留灵活性,还能做类型校验、超时控制、日志审计。如果连这个都不想维护,simpleeval 库是比裸 eval 更轻量的安全封装,它默认禁用所有危险操作,且可扩展函数白名单。
-
simpleeval.SimpleEval().eval("2 * x", names={"x": 5})→10 - 它不支持语句、无
__builtins__、无法访问闭包外变量,天然受限 - 但如果你需要
for循环或自定义类实例,它也无能为力——那说明你已经超出表达式范畴,该换设计了
动态执行不是语法糖,是信任边界的主动撕裂。哪怕只对内部运营人员开放,也要按生产环境权限模型来约束:最小作用域、最短生命周期、最细粒度审计。否则,一行 eval 就是系统里一颗哑弹。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











