Python 的动态代码执行机制:exec、eval、compile,这是 Python 运行时最具“元编程”特性的部分之一。
动态代码执行
Python 是动态语言,代码本身可以在运行时被生成、修改、再执行。这类能力主要通过三个内置函数实现:
| 函数 |
用途 |
eval(expr) |
计算一个表达式字符串,返回结果 |
exec(code) |
执行一段任意 Python 代码,不返回值 |
compile(source, filename, mode) |
把字符串或 AST 编译成可执行代码对象,可配合 exec/eval 使用 |
这种机制是 Python 解释器的动态执行接口,常用于:
- 动态生成代码,如 DSL、模板、动态函数;
- 交互式解释器 / 脚本执行器;
- 元编程 / 插件系统;
- 调试、沙盒执行。
eval():动态求值表达式
语法
1
| eval(expression, globals=None, locals=None)
|
功能
- 只能执行“单个表达式”字符串,如
"2 + 3", "a * b + c" 等;
- 返回表达式求值的结果;
- 不支持语句,如
for、if、def 等。
参数说明
expression:字符串或编译对象;
globals:指定全局命名空间(字典);
locals:指定局部命名空间(字典)。
示例
1 2 3
| x, y = 2, 3 result = eval("x * y + 5") print(result)
|
带命名空间控制:
1 2
| env = {'a': 10, 'b': 20} print(eval("a + b", env))
|
exec():执行任意 Python 代码
语法
1
| exec(object, globals=None, locals=None)
|
功能
- 执行任意 Python 语句或代码块;
- 没有返回值,也即返回
None;
- 可定义函数、类、变量等;
- 常用于运行外部脚本或动态生成代码。
示例
1 2 3 4 5 6 7
| code = """ def greet(name): print(f"Hello, {name}!") """
exec(code) greet("Alice")
|
自定义作用域:
1 2 3
| context = {} exec("x = 10\ny = 20\nresult = x + y", context) print(context["result"])
|
compile():预编译为代码对象
语法
1
| compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1)
|
参数说明
-
source:字符串或 AST 对象
-
filename:错误提示中显示任意的文件名
-
mode:
'exec' — 执行多行语句(适合 exec)
'eval' — 执行单个表达式(适合 eval)
'single' — 单行交互语句(如 REPL)
示例
1 2 3 4
| code_str = "x * y + 2" code_obj = compile(code_str, "<expr>", "eval") result = eval(code_obj, {"x": 3, "y": 5}) print(result)
|
执行语句块:
1 2 3 4 5
| code_block = """ for i in range(3): print(i) """ exec(compile(code_block, "<loop>", "exec"))
|
三者对比与联系
| 特性 |
eval() |
exec() |
compile() |
| 可执行内容 |
单个表达式 |
任意语句块 |
编译源码为代码对象 |
| 是否返回值 |
✅(表达式结果) |
❌ |
-(配合 eval/exec 执行) |
| 可控制作用域 |
✅ |
✅ |
✅(间接) |
| 可重用性 |
❌ |
❌ |
✅(编译后多次执行) |
| 常见用途 |
表达式求值 |
动态脚本执行 |
性能优化 / 安全分析 |
安全风险问题
动态执行意味着:
任何字符串都可能成为“代码注入攻击”的入口。
例如:
1 2
| user_input = "os.system('rm -rf /')" eval(user_input)
|
防御策略:
-
不直接执行用户输入。
-
若必须执行,先过滤 / 限制命名空间:
1 2
| safe_env = {"__builtins__": None, "math": __import__("math")} eval("math.sqrt(16)", safe_env)
|
-
结合 ast.literal_eval():
安全地解析字符串字面量(仅支持 int, list, dict 等):
1 2 3
| import ast ast.literal_eval("[1, 2, 3]") ast.literal_eval("__import__('os').system('rm -rf /')")
|
常用实战案例
动态公式引擎
1 2 3 4
| formula = "price * (1 + tax)" context = {"price": 100, "tax": 0.15} result = eval(formula, {}, context) print(result)
|
运行配置脚本
1 2 3 4 5 6 7
| config_code = """ DB_USER = "admin" DB_PASS = "12345" """ config = {} exec(config_code, config) print(config["DB_USER"])
|
编译+缓存表达式
1 2 3
| code = compile("a ** 2 + b", "<calc>", "eval") for i in range(10): print(eval(code, {"a": i, "b": 1}))
|
性能与实践建议
性能
最佳实践
| 场景 |
推荐用法 |
| 动态计算表达式 |
eval(expr, safe_globals) |
| 执行模板代码 / 代码块 |
exec(source, namespace) |
| 大量重复动态执行 |
code = compile(...); eval(code) |
| 解析用户输入常量 |
ast.literal_eval() |
| 调试 / 动态环境 |
可结合 locals() 与 globals() |
总结表
| 要点 |
说明 |
eval() |
求值表达式并返回结果 |
exec() |
执行任意代码块,无返回值 |
compile() |
将源码编译为可重用代码对象 |
| 安全风险 |
避免执行不可信输入 |
| 性能优化 |
重复执行时用 compile() 缓存 |
| 替代方案 |
用字典调度、函数映射、ast.literal_eval() |