- 实现一个交互式命令行 REPL (Read-Eval-Print Loop)。
- 加载和执行存储在配置文件或数据库中的 Python 代码。
- 实现一个插件系统,动态加载和运行插件代码。
- 执行用户输入的代码(需要非常小心安全风险)。
Python 提供了几个内置函数和模块来实现这个功能,其中最核心和最常用的是 eval() 和 exec()。

下面我将详细介绍这些方法,并提供代码示例和最佳实践。
核心方法:eval() vs exec()
这两个函数功能相似,但用途不同,理解它们的区别至关重要。
| 特性 | eval(expression, globals=None, locals=None) |
exec(statement, globals=None, locals=None) |
|---|---|---|
| 主要用途 | 计算一个表达式 (expression) 的值。 | 执行一个或多个语句 (statement)。 |
| 返回值 | 返回表达式的计算结果。 | 总是返回 None。 |
| 输入示例 | "1 + 2 * 3""len('hello')" |
"x = 10""print('Hello')""for i in range(3): print(i)" |
| 适用场景 | 简单的、有返回值的代码片段。 | 复杂的、没有返回值或包含流程控制(如 if, for, def)的代码块。 |
使用 eval() 计算表达式
eval() 适合处理简单的、可以求值的代码,它会返回计算结果。
基本用法:

# 一个简单的算术表达式
code_str = "1 + 2 * 3"
result = eval(code_str)
print(f"执行 '{code_str}' 的结果是: {result}") # 输出: 执行 '1 + 2 * 3' 的结果是: 7
# 使用内置函数
code_str = "len('world')"
result = eval(code_str)
print(f"执行 '{code_str}' 的结果是: {result}") # 输出: 执行 'len('world')' 的结果是: 5
使用 globals 和 locals 提供命名空间:
这是一个非常重要的安全性和功能性实践,你不应该让执行的代码随意访问你主程序的全局和局部变量。
globals: 提供一个全局命名空间(字典)。locals: 提供一个局部命名空间(字典)。
示例:让代码访问特定的变量
# 定义一些变量
x = 10
y = 20
# 创建一个受限的全局命名空间,只包含 'x'
# 注意:我们故意不把 'y' 放进去
safe_globals = {'x': x, '__builtins__': None} # __builtins__ 可以用来限制内置函数
# 创建一个代码字符串,它试图使用 x 和 y
code_str = "x * 2 + y"
# 尝试执行
try:
result = eval(code_str, safe_globals)
except NameError as e:
print(f"执行失败: {e}") # 输出: 执行失败: name 'y' is not defined
# 如果我们想让它能访问 y,就需要把它也加入命名空间
safe_globals_with_y = {'x': x, 'y': y, '__builtins__': None}
result = eval(code_str, safe_globals_with_y)
print(f"执行 '{code_str}' 的结果是: {result}") # 输出: 执行 'x * 2 + y' 的结果是: 40
使用 exec() 执行语句块
exec() 用于执行更复杂的 Python 代码,这些代码通常不返回值,或者执行一系列操作(如赋值、循环、定义函数等)。
基本用法:
# 执行一个赋值语句
code_str = "message = 'Hello from exec()'"
exec(code_str)
# 现在变量 'message' 已经被创建
print(message) # 输出: Hello from exec()
# 执行一个包含循环的代码块
code_str = """
for i in range(3):
print(f"Loop iteration {i}")
"""
exec(code_str)
# 输出:
# Loop iteration 0
# Loop iteration 1
# Loop iteration 2
使用 globals 和 locals 捕获变量:
这是 exec() 最强大的功能之一,你可以让执行的代码块在指定的命名空间中创建或修改变量,然后你可以在主程序中访问这些变量。
# 准备一个空的命名空间
my_namespace = {}
# 执行一段代码,它会在 my_namespace 中创建变量
code_str = "a = 100; b = 'executed';"
exec(code_str, my_namespace) # 将 my_namespace 作为全局和局部命名空间
# 检查 my_namespace 的内容
print("my_namespace 的内容:", my_namespace)
# 输出: my_namespace 的内容: {'a': 100, 'b': 'executed'}
# 现在可以从 my_namespace 中获取变量
print(f"变量 a 的值是: {my_namespace['a']}")
print(f"变量 b 的值是: {my_namespace['b']}")
更高级的用法:compile()
对于需要多次执行的代码,使用 compile() 函数将其预编译成一个代码对象,然后用 exec() 或 eval() 执行,效率会更高。
compile(source, filename, mode)
source: 字符串形式的代码。filename: 代码来源的文件名(用于错误信息),如果代码不是来自文件,可以传入'<string>'。mode: 模式,决定了如何编译代码。'exec': 用于包含语句序列的代码块(如exec())。'eval': 用于单个表达式(如eval())。'single': 用于单个交互式语句(例如在 REPL 中输入的一行代码)。
示例:
code_str = """
def greet(name):
return f"Hello, {name}!"
result = greet("Alice")
"""
# 编译代码
code_object = compile(code_str, "<string>", "exec")
# 准备命名空间
namespace = {}
# 执行编译后的代码对象
exec(code_object, namespace)
# 访问在命名空间中创建的变量和函数
print(namespace['result']) # 输出: Hello, Alice!
print(namespace['greet']("Bob")) # 输出: Hello, Bob!
安全警告 ⚠️
绝对不要使用 eval() 或 exec() 来执行不可信的代码,例如来自互联网用户的输入!
这些函数会执行任何有效的 Python 代码,这可能导致严重的安全漏洞,
- 代码注入攻击: 攻击者可以输入恶意代码来删除你的文件、窃取数据或控制系统。
# 危险示例! user_input = input("请输入一个数学表达式: ") # 如果用户输入: __import__('os').system('rm -rf /') # 那么你的硬盘可能会被清空! result = eval(user_input)
如何安全地使用?
-
严格限制命名空间: 这是最重要的防御手段,只提供给代码执行所需的最小权限。
- 将
globals设置为一个只包含你允许访问的变量的字典。 - 将
__builtins__设置为None或一个安全的字典,可以完全禁用或限制所有内置函数(如open,__import__,exec等)。
# 相对安全的 eval 示例 allowed_names = {'x': 50, 'y': 10, 'sqrt': math.sqrt} code_str = "sqrt(x**2 + y**2)" # 创建一个安全的全局命名空间 safe_globals = {"__builtins__": None} safe_globals.update(allowed_names) result = eval(code_str, safe_globals) print(result) # 输出: 51.0 - 将
-
使用
ast.literal_eval(): 如果你只需要处理像字符串、数字、元组、列表、字典、布尔值和None这样的字面量,ast.literal_eval()是最安全的选择,它被设计为只解析数据结构,而不执行任何代码。import ast # 安全 data_str = "[1, 2, 'three', {'key': 'value'}]" data = ast.literal_eval(data_str) print(data) # 输出: [1, 2, 'three', {'key': 'value'}] # 危险,literal_eval 会拒绝执行 malicious_str = "__import__('os').system('ls')" try: ast.literal_eval(malicious_str) except ValueError as e: print(f"安全拦截: {e}") # 输出: 安全拦截: malformed node or string
| 函数 | 用途 | 返回值 | 安全性 |
|---|---|---|---|
eval() |
计算单个表达式的值。 | 表达式的结果。 | 非常危险,仅用于受信任的代码。 |
exec() |
执行一个或多个语句。 | None。 |
非常危险,仅用于受信任的代码。 |
compile() |
预编译代码,提高重复执行的效率。 | 代码对象。 | 本身不危险,但配合 eval/exec 使用时需谨慎。 |
ast.literal_eval() |
安全地计算字符串形式的 Python 字面量。 | 字面量的值。 | 推荐用于数据,绝对安全。 |
最佳实践:
- 首选
ast.literal_eval():如果你的目标是解析数据结构,而不是执行代码。 - 严格限制命名空间:如果必须使用
eval()或exec(),务必提供受控的globals和locals字典,并禁用__builtins__。 - 永远不要信任输入:假设所有来自外部的输入都是恶意的,并采取相应的预防措施。
