eval解析布尔表达式更慢,因其每次调用都要重新tokenize、构建AST、动态查变量,绕过编译优化;且存在RCE风险。推荐compile预编译或operator+函数组合方案。

eval 解析布尔表达式为什么反而更慢
直接用 eval() 执行像 "a and b or not c" 这类字符串,在多数场景下比原生 Python 布尔运算慢 5–20 倍。根本原因不是语法解析开销大,而是每次调用 eval() 都要:重新 tokenize、构建 AST、动态查找变量(通过 locals() 或传入的字典)、再逐节点求值——完全绕过了 Python 的编译器优化和字节码缓存。
更危险的是,eval() 会执行任意代码,哪怕你“过滤”了字符,只要输入含 "__import__" 或 "getattr" 就可能触发 RCE。线上服务中用它处理用户输入等于主动开后门。
用 compile + exec 预编译表达式提升 3–8 倍
如果必须从字符串生成逻辑(比如规则引擎),应避免反复调用 eval(),改用 compile() 一次编译、多次 exec() 或 eval() 执行:
expr = "a and b or not c"
# 编译一次,得到 code object
compiled = compile(expr, "<string>", "eval")
<h1>后续每次只需传入变量字典</h1><p>result = eval(compiled, {"<strong>builtins</strong>": {}}, {"a": True, "b": False, "c": True})
- 编译后的
compiled是可复用对象,不重复解析语法 - 务必限制
__builtins__为空字典,否则仍存在执行任意函数风险 - 变量必须显式传入第二参数(
globals)或第三参数(locals),不能依赖闭包或外层作用域 - 若表达式固定且变量名稳定,还可进一步用
functools.partial封装成函数,减少字典构造开销
真·高性能方案:用 operator 模块 + 函数组合
当条件逻辑来自配置(如 JSON 规则),优先把字符串转成函数,而非每次运行时解析:
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
立即学习“Python免费学习笔记(深入)”;
from operator import and_, or_, not_ <h1>把 "and(a, or(b, not(c)))" 映射为函数调用链</h1><p>def build_rule(rule_ast): op = rule<em>ast["op"] if op == "and": return lambda a, b: and</em>(a, b) elif op == "not": return lambda x: not_(x)</p><h1>... 其他操作符</h1><p>
- AST 结构比字符串安全得多,无代码注入可能
- 生成的 lambda 可被 Python JIT(如 PyPy)或 CPython 的调用优化识别
- 配合
functools.lru_cache缓存高频规则的函数对象,避免重复构建 - 比
compile()方案再快 1.5–3 倍,尤其在大量重复规则+少量变量组合时优势明显
别忽略短路求值和早期退出的代价
无论用哪种方式,原生 and/or 的短路行为(如 False and heavy_computation() 不执行右侧)在 eval() 或预编译方案中默认保留,但 operator 方案需手动实现:
-
and_(a(), b())一定会调用两个函数,失去短路能力 - 若右侧计算昂贵(如查数据库、读文件),必须改写为
lambda: a() and b()再编译,或用嵌套if表达式 - JSON 规则引擎里建议加字段
"short_circuit": true,生成不同执行路径
真正卡顿的往往不是解析,而是没意识到某些条件本可跳过——先画出条件依赖图,再决定哪部分值得预编译、哪部分该用 if 分支硬编码。

















