想根据用户输入、配置文件或者其他动态条件,在函数里灵活地创建不同名字的变量?
很多Python萌新会想到exec()这个“魔法”函数。它确实能“变”出变量,但也像一把双刃剑,用不好会伤到自己!今天就来聊聊exec()在函数内设置变量的门道和那些你必须知道的“坑”。
一、 什么是exec()?它能干嘛
简单说,exec()是一个Python内置函数。它的“超能力”是:能把一个字符串当作完整的Python代码来执行! 就像你写在.py文件里的代码一样。
想象一下:你有一个字符串 "x = 11 + 22"。如果你把它丢给exec():
exec("x = 11 + 22")
print(x) # 输出 33
神奇吗?x这个变量就被创建并赋值了!它似乎打破了“变量必须先定义后使用”的常规。
二、 为什么有人想在函数里用exec()造变量
核心诉求是:动态变量名!
- 场景举例1: 你写一个函数process_data(data, prefix),想把处理后的数据存成prefix + "_result"格式的变量。如果prefix="user",你就想创建变量user_result。变量名user_result是运行时根据prefix决定的!
- 场景举例2: 从配置文件读取一批字段名和值,想在函数里直接生成对应的变量。
三、 新手容易掉进去的“坑”
很多教程(包括网上一些片段)会这样写:
def create_var(value):
exec(f"new_var = {value}") # 1. 用exec创建变量
return new_var # 2. 直接返回这个变量
result = create_var(100)
print(result) # 期待输出100?
结果呢?多半会报错:**NameError: name 'new_var' is not defined**
为什么?这就是exec()在函数内最大的“坑”——作用域问题!
1、默认行为: 当exec()单独执行一个字符串(不带额外参数)时:
- 它确实会在当前局部作用域创建变量。
- 但是!这个“局部作用域”是exec()自己执行代码时的局部命名空间,并不等同于你函数create_var的局部命名空间!
2、后果: exec()执行完毕后,它在自己那个小空间里创建的new_var就消失了。函数主体代码里的return new_var根本找不到这个名字,于是报错。
四、 如何在函数内“安全”获取exec()创建的变量
既然exec()创建的变量在默认的局部空间里,我们就要想办法进入那个空间去拿!这就需要用到两个内置字典:
- locals(): 返回当前局部作用域的变量字典。
- globals(): 返回当前全局作用域的变量字典。
改进版写法:
def create_var_dynamic(var_name, value):
# 1. 准备要执行的代码字符串
code = f"{var_name} = {value}" # 注意:这里字符串拼接有风险!后面讲
# 2. 获取函数当前的局部作用域字典
local_scope = {}
# 3. 关键一步:把空字典 local_scope 同时传给 locals 和 globals 参数
exec(code, globals(), local_scope)
# 4. 现在,变量存在于 local_scope 这个字典里了!
return local_scope[var_name] # 从字典里取出我们创建的变量值
# 使用
var_name = "calculated_total" # 动态变量名
result = create_var_dynamic(var_name, 11 + 22) # 动态值
print(result) # 输出: 33
# 注意!在函数外部,变量名 `calculated_total` 并不存在!
# 我们只是通过函数返回了它的值。
关键解释:
1、我们创建了一个空的字典 local_scope = {}。 2、调用 exec(code, globals(), local_scope) 时:
- globals() 获取了全局作用域(提供基础环境)。
- local_scope 这个空字典被用作执行代码的局部命名空间。
- exec() 执行 code 时,所有在代码里创建的变量(如 calculated_total)都会被塞进 local_scope 这个字典里。 3、 执行完毕后,我们想要的变量值就安静的躺在 local_scope[var_name] 里,直接返回它即可。
五、 exec() 的致命弱点:安全风险
上面解决了作用域问题,但exec()最大的“坑”才刚浮出水面——极其危险的安全漏洞!
问题根源: exec() 会无条件执行你传给它的任何字符串代码!如果这个字符串来自不可信的来源(用户输入、网络、文件),攻击者可以构造恶意字符串。
恐怖示例:
# 假设用户输入恶意值
user_input = "100; import os; os.system('rm -rf /')" # 模拟删除系统文件(极端危险!)
# 或者更隐蔽的: user_input = "100; __import__('os').system('malicious_command')"
# 如果直接拼接到exec
result = create_var_dynamic("hacked", user_input)
# 执行后,不仅返回值,你的系统可能已经被攻击了!
exec(f"{var_name} = {user_input}") 会把用户输入的 import os; os.system(...) 也当作代码执行!后果不堪设想。
为什么字典 (dict) 是更好的选择?
对于动态命名需求,99%的情况,字典都是更安全、更清晰、更高效的选择!
def process_data(data, prefix):
# 创建一个空字典存储结果
results = {}
# ... 处理data ...
# 动态键名:完全满足“动态命名”需求
key = prefix + "_result"
results[key] = processed_data
return results # 安全返回整个字典
# 使用
data = {...}
output = process_data(data, "user")
user_result = output["user_result"] # 安全获取你想要的值
优势:
- 安全: 字典的键只是数据,不会被当作代码执行。
- 清晰: 所有动态“变量”都规规矩矩待在字典里,结构一目了然。
- 灵活: 轻松存储、修改、遍历各种动态命名的数据。
- 作用域友好: 没有exec()那些诡异的作用域问题。
Python的exec()就像一把锋利的“瑞士军刀”,功能强大但也容易割伤自己。当你真正成长为Python高手,理解了代码执行的底层原理和安全风险的方方面面,再谨慎地考虑是否真的需要动用exec()这把“双刃剑”吧!安全第一,快乐编码!