为什么Python 3对exec的更改导致此代码失效?
我在StackOverflow上看了很多关于“Python exec”的讨论,但没找到能解决我问题的帖子。如果之前有人问过这个问题,我在这里先说声抱歉。我的问题是:
# Python 2.6: prints 'it is working'
# Python 3.1.2: "NameError: global name 'a_func' is not defined"
class Testing(object):
def __init__(self):
exec("""def a_func():
print('it is working')""")
a_func()
Testing()
# Python 2.6: prints 'it is working'
# Python 3.1.2: prints 'it is working'
class Testing(object):
def __init__(self):
def a_func():
print('it is working')
a_func()
Testing()
因为标准的函数定义在Python的两个版本中都能正常工作,所以我猜问题可能出在exec的用法上。我查阅了2.6和3版本的API文档,还看了“Python 3.0的新特性”页面,但没有找到代码出错的原因。
2 个回答
为了补充上面的回答,以防万一。如果exec
是在某个函数里,我建议使用三参数的版本,像这样:
def f():
d = {}
exec("def myfunc(): ...", globals(), d)
d["myfunc"]()
这是最干净的解决方案,因为它不会影响到你周围的任何命名空间。相反,myfunc
被存储在一个明确的字典d
里。
你可以通过以下方式查看每个Python版本生成的字节码:
>>> from dis import dis
而对于每个解释器,你可以这样查看:
#Python 3.2
>>> dis(Testing.__init__)
...
5 10 LOAD_GLOBAL 1 (a_func)
...
#Python 2.7
>>> dis(Testing.__init__)
...
5 8 LOAD_NAME 0 (a_func)
...
从中可以看到,Python 3.2会查找一个名为a_func
的全局变量(LOAD_GLOBAL),而Python 2.7则会先在本地范围内查找(LOAD_NAME),如果找不到再去全局查找。
如果你在exec
之后执行print(locals())
,你会发现a_func
是在__init__
函数内部创建的。
我其实不太明白为什么要这样做,但这似乎是处理符号表的一种变化。
顺便说一下,如果你想在__init__
方法的顶部创建一个a_func = None
,让解释器知道这是一个局部变量,这样做是行不通的,因为字节码现在会变成LOAD_FAST
,它不会去查找,而是直接从一个列表中获取值。
我看到的唯一解决办法是把globals()
作为exec
的第二个参数,这样就会把a_func
创建为一个全局函数,可以通过LOAD_GLOBAL
指令访问。
编辑
如果你去掉exec
语句,Python 2.7会把字节码从LOAD_NAME
改为LOAD_GLOBAL
。所以,在Python 2.x中使用exec
,你的代码会变得更慢,因为它需要在本地范围内查找变化。
而Python 3中的exec
不是一个关键字,解释器无法确定它是否真的在执行新代码,还是在做其他事情……所以字节码不会改变。
例如:
>>> exec = len
>>> exec([1,2,3])
3
总结
如果你不在乎结果被添加到全局命名空间,可以用exec('...', globals())
来解决这个问题。