Python 的动态代码执行机制execevalcompile,这是 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" 等;
  • 返回表达式求值的结果;
  • 不支持语句,如 forifdef 等。

参数说明

  • expression:字符串或编译对象;
  • globals:指定全局命名空间(字典);
  • locals:指定局部命名空间(字典)。

示例

1
2
3
x, y = 2, 3
result = eval("x * y + 5")
print(result) # 11

带命名空间控制:

1
2
env = {'a': 10, 'b': 20}
print(eval("a + b", env)) # 30

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") # 输出 Hello, Alice!

自定义作用域:

1
2
3
context = {}
exec("x = 10\ny = 20\nresult = x + y", context)
print(context["result"]) # 30

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) # 17

执行语句块:

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. 若必须执行,先过滤 / 限制命名空间

    1
    2
    safe_env = {"__builtins__": None, "math": __import__("math")}
    eval("math.sqrt(16)", safe_env) # ✅ 仅能使用 math
  3. 结合 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) # 115.0

运行配置脚本

1
2
3
4
5
6
7
config_code = """
DB_USER = "admin"
DB_PASS = "12345"
"""
config = {}
exec(config_code, config)
print(config["DB_USER"]) # "admin"

编译+缓存表达式

1
2
3
code = compile("a ** 2 + b", "<calc>", "eval")
for i in range(10):
print(eval(code, {"a": i, "b": 1}))

性能与实践建议

性能

  • eval / exec 会触发解释器重新编译,成本较高;

  • 对重复执行的动态代码,使用 compile() 缓存结果更高效;

  • 如果可能,优先考虑:

    • 使用函数映射;
    • 使用 getattr() / operator 模块;
    • 用字典替代 if-else 调度。

最佳实践

场景 推荐用法
动态计算表达式 eval(expr, safe_globals)
执行模板代码 / 代码块 exec(source, namespace)
大量重复动态执行 code = compile(...); eval(code)
解析用户输入常量 ast.literal_eval()
调试 / 动态环境 可结合 locals()globals()

总结表

要点 说明
eval() 求值表达式并返回结果
exec() 执行任意代码块,无返回值
compile() 将源码编译为可重用代码对象
安全风险 避免执行不可信输入
性能优化 重复执行时用 compile() 缓存
替代方案 用字典调度、函数映射、ast.literal_eval()