杰瑞科技汇

python程序执行python指令

  • 实现一个交互式命令行 REPL (Read-Eval-Print Loop)。
  • 加载和执行存储在配置文件或数据库中的 Python 代码。
  • 实现一个插件系统,动态加载和运行插件代码。
  • 执行用户输入的代码(需要非常小心安全风险)。

Python 提供了几个内置函数和模块来实现这个功能,其中最核心和最常用的是 eval()exec()

python程序执行python指令-图1
(图片来源网络,侵删)

下面我将详细介绍这些方法,并提供代码示例和最佳实践。


核心方法: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() 适合处理简单的、可以求值的代码,它会返回计算结果。

基本用法:

python程序执行python指令-图2
(图片来源网络,侵删)
# 一个简单的算术表达式
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

使用 globalslocals 提供命名空间:

这是一个非常重要的安全性和功能性实践,你不应该让执行的代码随意访问你主程序的全局和局部变量。

  • 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

使用 globalslocals 捕获变量:

这是 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)

如何安全地使用?

  1. 严格限制命名空间: 这是最重要的防御手段,只提供给代码执行所需的最小权限。

    • 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
  2. 使用 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(),务必提供受控的 globalslocals 字典,并禁用 __builtins__
  • 永远不要信任输入:假设所有来自外部的输入都是恶意的,并采取相应的预防措施。
分享:
扫描分享到社交APP
上一篇
下一篇