首先我们来做一个小实验:
>>>a=3
>>>exec('b = a +1')
>>>print(b)
>>>14
然后我们在函数体内做同样的实验:
>>>def test():
... a = 13
... exec('b = a + 1')
... print(b)
...
运行结果却出现了错误:
NameError: global name ‘b’ is not defined
要解决此类问题,需要使用locals()函数在调用exec()之前获取一个保存了局部变量的字典,紧接着,就可以从本地字典中提取出修改过的值。示例如下:
def test():
a = 13
loc = locals()
exec('b = a + 1')
b = loc['b']
print(b)
运行这个函数之后结果为正确的 14
下面我们来进行一番讨论
要一般编程过程中要正确使用exec()其实是非常具有技巧性的,事实上,大多数考虑使用exec()的情况下,可能存在更加优雅的解决方案(例如装饰器、闭包、元类等)。
如果必须要使用exec(),那么可以参考以下原则:
默认情况下,exec()在调用方的局部或者全局作用域中执行代码。然而在函数内部,传递给exec()的局部作用是一个字典,而这个字典是实际局部变量的一份拷贝。因此,如果exec()中执行的代码对全局变量做出了任何修改,这个修改绝不会反应到实际的局部变量中去。以下示例将作出解释:
def test1():
a = 0
exec('a += 1')
print(a)
执行此函数结果为 0 ,可见当调用locals()来获取局部变量时,传递给exec()的是局部变量的拷贝。而在exec()执行完毕之后,通过检查字典的值,就能获取到修改过的变量值。下例可验证这一点:
def test2():
a = 0
loc = locals()
print('before:',loc)
exec('a += 1')
print('after:',loc)
#a = loc['a']
print(a)
执行结果如下:
before: {'a': 0}
after: {'a': 1, 'loc': {...}}
0
观察最后一步的输出可以得知,除非从loc中将修改过的值写回x,否则变量x会保持不变。
每次使用locals()时都要小心操作的顺序问题。每次调用时,locals()将会接受局部变量的当前值,然后覆盖字典中的对应条目。请看以下示例:
def test3():
a = 0
loc = locals()
print(loc)
exec('a += 1')
print(loc)
locals()
print(loc)
执行结果如下
{'a': 0}
{'loc': {...}, 'a': 1}
{'loc': {...}, 'a': 0}
注意最后对locals()的调用是如何导致x被覆盖的。
除了使用locals()之外,另一种可选的方式是自己创建字典并传递给exec()。示例如下:
def test4():
a = 13
loc = {'a':a}
glb={ }
exec('b = a + 1',glb,loc)
b = loc['b']
print(b)
最后的运行结果为14
对于大部分针对exec()的应用,这就够了。我们需要确保exec()中访问的变量在全局和局部字典中经过你恰当的初始化。
最后提示:
在使用exec()时,想一想其他的可选方案,就像讨论一开始列出的类似选项