Python exec 内置语句(一文讲透)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观

在 Python 编程中,exec 是一个强大但容易被低估的内置语句。它允许开发者动态执行字符串形式的代码片段,这一特性在构建可扩展的应用程序、实现插件系统或处理用户自定义逻辑时尤为重要。对于编程初学者和中级开发者来说,理解 exec 的工作原理及其应用场景,能够显著提升代码设计的灵活性和创造力。本文将通过循序渐进的讲解、生动的比喻和实际案例,帮助读者掌握 exec 的核心用法与注意事项。


一、exec 的基础概念与语法

1.1 什么是 exec

exec 是 Python 内置的语句(statement),用于执行动态生成的代码字符串。它与 eval 类似,但功能更强大:eval 只能执行表达式并返回结果,而 exec 可以执行完整的语句块(如循环、函数定义等)。

比喻
可以将 exec 想象为一个“代码指挥官”,它能够接收一段字符串形式的指令(代码),并像人类程序员一样逐行执行这些指令。例如:

code_str = "print('Hello, exec!')"  
exec(code_str)  # 输出:Hello, exec!  

1.2 语法结构

exec 的基本语法如下:

exec(object, globals=None, locals=None, /)  
  • object:要执行的代码字符串或编译后的代码对象。
  • globalslocals:可选参数,用于指定代码执行时的全局和局部作用域。默认情况下,exec 会使用调用者的作用域。

注意
exec 是语句而非函数,因此在 Python 3.x 中无需加括号(如 exec code_str 是 Python 2 的用法,在 Python 3 中应写成 exec(code_str))。


二、exec 的核心功能与典型用例

2.1 动态执行代码字符串

exec 最直接的应用场景是执行动态生成的代码。例如,当需要根据用户输入或配置文件中的指令执行操作时:

案例 1:动态执行用户输入的代码

user_code = input("请输入要执行的代码:")  
exec(user_code)  

注意
此案例存在安全风险(稍后会详细讨论),仅用于演示基础用法。

2.2 定义函数或类

通过 exec,可以在运行时动态生成函数或类:

案例 2:动态创建函数

code_str = """  
def greet(name):  
    print(f"你好,{name}!")  
"""  
exec(code_str)  
greet("Alice")  # 输出:你好,Alice!  

此示例展示了 exec 可以像程序员一样定义新函数,并将其加入当前作用域。

2.3 作用域控制:globalslocals

通过 globalslocals 参数,可以精确控制代码执行时的变量作用域。

案例 3:隔离变量污染

my_globals = {}  
my_locals = {}  

code_str = "x = 42"  
exec(code_str, my_globals, my_locals)  

print(my_locals["x"])  # 输出:42  
print("x" in locals())  # 输出:False(当前作用域无 x)  

此案例中,x 变量仅存在于 my_locals 中,避免了对全局或当前作用域的污染。


三、exec 的高级应用与技巧

3.1 结合 compile() 优化性能

如果需要多次执行同一段代码,可以先将其编译为代码对象,避免重复解析字符串的开销:

案例 4:编译并多次执行代码

code_str = "print('Hello!')"  
compiled_code = compile(code_str, "<string>", "exec")  

for _ in range(3):  
    exec(compiled_code)  

3.2 模块化动态代码

通过 execimport 结合,可以实现类似插件系统的功能。例如,动态加载用户自定义的模块:

案例 5:动态加载插件

with open("plugin_code.py", "r") as f:  
    code_str = f.read()  

exec(code_str, globals())  # 将插件代码加入全局作用域  

if "plugin_function" in globals():  
    plugin_function()  

四、exec 的风险与注意事项

4.1 安全风险:代码注入攻击

由于 exec 可以执行任意代码,如果输入未经过滤,可能成为攻击入口。例如,用户输入的代码可能窃取敏感信息或破坏系统:

危险示例

user_input = "import os; os.system('rm -rf /')"  
exec(user_input)  # 危险!会删除系统文件  

解决方案

  • 沙箱环境:限制代码执行的权限(如禁用危险模块)。
  • 白名单过滤:仅允许执行特定的代码片段。

4.2 作用域污染与变量泄漏

如果未正确管理 globalslocals,代码可能意外修改外部变量,导致难以调试的 bug:

案例 6:作用域污染示例

x = 10  
exec("x = 20")  # 默认使用当前作用域  
print(x)  # 输出:20(原变量被修改)  

解决方案
通过隔离 locals 避免污染:

exec("x = 20", {}, {"x": 10})  
print(x)  # 仍输出 10  

4.3 性能问题

频繁使用 exec 可能导致性能下降,因为每次执行都需要解析字符串。对于循环中的代码,建议优先使用其他方式(如函数或类)。


五、execeval 的对比

5.1 核心区别

特性execeval
返回值无(仅执行代码)返回表达式的结果
语法支持支持完整语句(如 fordef仅支持单个表达式
适用场景动态执行复杂逻辑计算简单表达式的结果

比喻

  • exec 像是“全功能厨房”,可以烹饪整道菜;
  • eval 则像“微波炉”,只能加热已有的食物。

5.2 示例对比

exec("""  
for i in range(3):  
    print(i)  
""")  

result = eval("2 + 3 * 5")  
print(result)  # 输出:17  

六、常见问题解答

Q1:如何避免 exec 的安全风险?

  • 沙箱技术:使用第三方库(如 RestrictedPython)限制代码权限。
  • 输入过滤:仅允许特定的关键词和函数。

Q2:exec 是否支持异步代码?

是的,但需要将代码字符串中的 async 关键字显式声明,例如:

exec("async def my_coroutine(): ...")  

Q3:execglobals() 的关系是什么?

exec 默认使用调用者的作用域作为全局作用域,因此修改 globals() 中的变量会影响外部环境。


结论

exec 是 Python 中一个功能强大的工具,它允许开发者突破静态代码的限制,实现动态执行、插件化设计等高级功能。然而,其潜在的安全风险和作用域问题也要求开发者谨慎使用。通过合理控制作用域、过滤输入和结合其他技术(如代码编译),可以最大化 exec 的优势并规避风险。对于希望深入掌握 Python 动态特性的开发者来说,exec 是一个值得深入研究的工具。

最后提醒
在生产环境中使用 exec 时,务必优先考虑安全性,避免直接执行不可信的输入代码。

最新发布