Python exec 内置语句:动态执行代码的强大工具
在 Python 的众多内置函数中,exec 可能是最容易被误解、也最容易被滥用的一个。它不像 print 那样直观,也不像 len 那样日常高频使用。但一旦掌握,它能让你的程序具备“自我修改”“动态生成代码”甚至“运行用户输入脚本”的能力。
你有没有想过,一段字符串内容,可以像程序一样被“执行”?比如:
code = "print('Hello, 动态世界!')"
exec(code)
这段代码会输出 Hello, 动态世界!。没错,exec 就是让 Python 把字符串当作代码来运行的“执行器”。
今天我们就来深入聊聊这个强大的内置语句——Python exec 内置语句,从基础用法到实战技巧,再到安全风险,一步步带你掌握它的真谛。
exec 的基本语法与运行机制
exec 的语法非常简单:
exec(object, globals=None, locals=None)
object:必须是字符串或代码对象,包含要执行的 Python 代码。globals:全局命名空间,可选参数。locals:局部命名空间,可选参数。
想象一下,exec 就像一个“代码翻译官”——它接收一段“文字版”代码(字符串),然后翻译成机器能理解的指令,再执行。
示例:最简单的 exec 使用
code_str = "x = 10\ny = 20\nprint(x + y)"
exec(code_str)
输出结果:
30
💡 中文注释:
code_str是一个包含多行代码的字符串,其中x = 10和y = 20是赋值语句,print(x + y)是输出语句。exec会把这段字符串当作完整的 Python 脚本运行,因此变量x和y被成功创建并计算出结果。- 注意:
exec并不会返回任何值,它只是“执行”动作,不产生返回值。
作用域与命名空间:exec 的“舞台”和“演员”
exec 的运行依赖于两个关键概念:全局命名空间(globals)和局部命名空间(locals)。它们决定了代码执行时变量的可见范围。
作用域示例:全局 vs 局部
global_var = "我是全局变量"
code = """
print(global_var) # 可以访问全局变量
local_var = "我是局部变量"
print(local_var)
"""
exec(code)
💡 中文注释:
global_var在exec外定义,所以exec内部可以访问它。local_var是在exec内部创建的,执行结束后就“退出舞台”了,外部无法访问。- 这说明
exec的变量作用域是“临时的”——只在代码块内有效。
使用命名空间控制变量范围
my_globals = {}
my_locals = {}
exec("x = 100; y = 200", my_globals, my_locals)
print(my_globals) # {'__builtins__': <module '__builtin__' (built-in)>}
print(my_locals) # {'x': 100, 'y': 200}
💡 中文注释:
my_globals和my_locals是两个独立的字典,用来存放变量。exec执行时,变量被存入这两个字典中,不会污染当前环境。- 这是安全使用
exec的关键:隔离作用域,避免意外变量冲突。
动态代码生成:让程序“自己写代码”
exec 最强大的应用场景之一是“动态生成代码”。比如,根据用户输入或配置文件,动态创建函数、类或循环逻辑。
示例:动态创建函数
function_code = """
def greet(name):
return f'你好,{name}!欢迎使用 Python exec 内置语句'
"""
exec(function_code)
print(greet("小明")) # 你好,小明!欢迎使用 Python exec 内置语句
💡 中文注释:
function_code是一个字符串,定义了一个函数greet。exec把这个字符串当作代码运行,于是函数就被“创建”了。- 之后,你可以像调用普通函数一样调用
greet。- 这种方式在插件系统、配置驱动程序中非常实用。
实战案例:配置驱动的自动化脚本
假设你有一个任务配置文件(如 JSON 或字符串),你想根据配置动态执行不同逻辑。
案例:根据配置运行不同计算
config = """
operation = input('请输入操作类型(加法/乘法):')
a = float(input('请输入第一个数:'))
b = float(input('请输入第二个数:'))
if operation == '加法':
result = a + b
elif operation == '乘法':
result = a * b
else:
result = '无效操作'
print(f'结果是:{result}')
"""
exec(config)
💡 中文注释:
- 这段代码模拟了一个可配置的任务系统。
- 用户输入操作类型和数值,
exec会动态执行对应的逻辑。- 实际项目中,你可以从文件、数据库或网络读取配置,再用
exec执行。- 注意:这种模式在生产环境中需严格校验输入,防止安全风险。
安全风险与最佳实践:别让 exec 成为“后门”
exec 是一把双刃剑。它强大,但危险。一旦用户输入了恶意代码,后果不堪设想。
危险示例:恶意代码注入
user_input = "import os; os.system('rm -rf /')"
exec(user_input) # 这会尝试删除系统根目录!
⚠️ 警告:
上面的代码虽然在本地可能不会执行(因为权限问题),但一旦在服务器上运行,后果严重。
exec可以执行任意代码,包括删除文件、上传后门、获取敏感信息等。
安全使用建议
- 绝不直接执行用户输入,除非经过严格过滤和沙箱隔离。
- 使用
globals和locals限制作用域,避免访问os、sys等敏感模块。 - 使用
ast模块代替exec:如果只是计算表达式,优先用eval(注意eval也有风险)或ast.literal_eval。 - 在沙箱环境中运行:使用
restrictedpython或PyPy的沙箱机制。
安全示例:限制执行环境
safe_globals = {
'__builtins__': {
'print': print,
'len': len,
'abs': abs,
'int': int,
'float': float,
'str': str,
}
}
code = "x = 10 + 20; print(x)"
exec(code, safe_globals) # 安全执行
💡 中文注释:
safe_globals中只保留了安全的内置函数,移除了os、sys等危险模块。- 即使用户输入
import os,也会报错,因为os不在__builtins__中。- 这是保护
exec安全性的核心手段。
exec 与 eval 的区别:你真的分清了吗?
很多人混淆 exec 和 eval。它们都用于执行字符串代码,但用途不同。
| 特性 | exec | eval |
|---|---|---|
| 用途 | 执行任意 Python 语句(如赋值、循环、函数定义) | 只能执行表达式,返回值 |
| 返回值 | 无返回值(返回 None) | 返回表达式的结果 |
| 示例 | exec("x = 10") |
eval("2 + 3") → 返回 5 |
对比示例
exec("a = 10; b = 20")
print(a + b) # 输出 30
result = eval("a + b") # 报错!a、b 未定义
💡 中文注释:
exec能创建变量,eval不能。eval适用于数学计算、配置解析等场景,而exec适合复杂逻辑动态生成。- 如果你只需要计算一个表达式,优先用
eval,更安全。
总结:Python exec 内置语句的正确打开方式
exec 是 Python 中一个非常强大但需谨慎使用的内置语句。它让程序具备了“自我演化”的能力,能动态执行代码,提升灵活性。
但正如所有强大工具一样,它也伴随着风险。我们不能因为它的便利就滥用,尤其是在处理用户输入或外部配置时。
正确使用 exec 的原则是:
- 仅在必要时使用,优先考虑静态代码或配置文件。
- 始终使用
globals和locals隔离作用域。 - 严格限制可用的内置函数,避免导入危险模块。
- 在生产环境中,尽量用
ast或配置解析替代exec。
掌握 exec,不是为了“炫技”,而是为了在合适场景下,用对工具解决复杂问题。愿你在使用 Python exec 内置语句时,既能享受它的强大,也能守住安全的底线。