如何在使用`exec`调用时更新局部变量?
我原以为这段代码会输出3,但实际上它输出的是1:
# Python3
def f():
a = 1
exec("a = 3")
print(a)
f()
# 1 Expected 3
3 个回答
你不能通过 exec
来改变函数里的局部变量,原因可以简单总结如下:
exec
是一个函数,它的局部作用域和它被调用时最内层的作用域是共享的。- 每当你在一个函数的作用域内定义一个新对象时,它会在这个函数的局部命名空间中可用,也就是说,它会修改
local()
字典。当你在exec
中定义一个新对象时,它的效果大致相当于下面的内容:
from copy import copy
class exec_type:
def __init__(self, *args, **kwargs):
# default initializations
# ...
self.temp = copy(locals())
def __setitem__(self, key, value):
if var not in locals():
set_local(key, value)
self.temp[key] = value
temp
是一个临时的命名空间,每次调用 exec
时都会重置。
- Python 查找名字的顺序是从局部命名空间开始的,这个过程被称为 LEGB 规则。Python 会先查找局部命名空间,然后是外层作用域,再接着是全局命名空间,最后是内置命名空间。
一个更全面的例子可能是这样的:
g_var = 5
def test():
l_var = 10
print(locals())
exec("print(locals())")
exec("g_var = 222")
exec("l_var = 111")
exec("print(locals())")
exec("l_var = 111; print(locals())")
exec("print(locals())")
print(locals())
def inner():
exec("print(locals())")
exec("inner_var = 100")
exec("print(locals())")
exec("print([i for i in globals() if '__' not in i])")
print("Inner function: ")
inner()
print("-------" * 3)
return (g_var, l_var)
print(test())
exec("print(g_var)")
输出:
{'l_var': 10}
{'l_var': 10}
局部变量是相同的。
{'l_var': 10, 'g_var': 222}
在添加了 g_var
并改变 l_var
后,只添加了 g_var
,而 l_var
保持不变。
{'l_var': 111, 'g_var': 222}
l_var
被改变是因为我们在一次调用 exec
时同时改变和打印了局部变量。
{'l_var': 10, 'g_var': 222}
{'l_var': 10, 'g_var': 222}
在函数的局部变量和 exec
的局部变量中,l_var
都没有改变,而 g_var
被添加了。
Inner function:
{}
{'inner_var': 100}
{'inner_var': 100}
inner_function
的局部变量和 exec
的局部变量是相同的。
['g_var', 'test']
全局命名空间只包含 g_var
和函数名(排除特殊方法后)。
---------------------
(5, 10)
5
如果你在一个方法里面,你可以这样做:
# python 2 or 3
class Thing():
def __init__(self):
exec('self.foo = 2')
x = Thing()
print(x.foo)
这个问题在Python3的错误列表中有一些讨论。要实现这种行为,你需要这样做:
def foo():
ldict = {}
exec("a=3",globals(),ldict)
a = ldict['a']
print(a)
如果你查看Python3关于exec
的文档,你会看到以下说明:
默认的局部变量就像下面的
locals()
函数描述的那样:不应该尝试修改默认的局部变量字典。如果你需要在函数执行后查看代码对局部变量的影响,请传递一个明确的局部变量字典。
这意味着,单参数的exec
不能安全地执行任何会绑定局部变量的操作,包括变量赋值、导入、函数定义、类定义等等。如果使用global
声明,它可以对全局变量进行赋值,但不能对局部变量。
回到这个错误报告中的具体信息,Georg Brandl说:
在不产生多个后果的情况下,动态修改函数的局部变量是不可能的:通常,函数的局部变量并不是存储在字典中,而是存储在一个数组中,这个数组的索引是在编译时根据已知的局部变量确定的。这与exec添加的新局部变量发生冲突。旧的exec语句绕过了这个问题,因为编译器知道如果在函数中出现没有全局/局部参数的exec,那么那个命名空间将是“未优化”的,也就是说不使用局部变量数组。由于exec()现在是一个普通函数,编译器不知道“exec”可能绑定到什么,因此无法特别处理它。
强调是我的。
所以,总的来说,Python3通过默认不允许这种行为,能够更好地优化局部变量的使用。
为了完整起见,正如上面评论中提到的,这在Python 2.X中确实按预期工作:
Python 2.6.2 (release26-maint, Apr 19 2009, 01:56:41)
[GCC 4.3.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> def f():
... a = 1
... exec "a=3"
... print a
...
>>> f()
3