我在SO上查看了无数的'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文档 exec
并且还阅读了“Python 3.0中的新功能”页面,并且看不出代码中断的任何原因。
您可以看到每个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搜索名为的全局值(LOAD_GLOBAL) a_func
2.7在搜索全局范围之前首先搜索本地范围(LOAD_NAME)。
如果你这样做 print(locals())
之后 exec
,你会看到的 a_func
是在里面创建的 __init__
功能。
我真的不知道为什么这样做,但似乎是如何改变 符号表 被处理。
BTW,如果想创建一个 a_func = None
在你的 __init__
使解释器知道它是一个局部变量的方法,因为字节码现在将不起作用 LOAD_FAST
并且不进行搜索但直接从列表中获取值。
我看到的唯一解决方案是添加 globals()
作为第二个参数 exec
,这将创造 a_func
作为一个全局函数,可以通过访问 LOAD_GLOBAL
操作码。
编辑
如果删除了 exec
声明,Python2.7更改字节码 LOAD_NAME
至 LOAD_GLOBAL
。所以,使用 exec
,你的代码在Python2.x上总是会变慢,因为它必须在本地范围内搜索更改。
作为Python3的 exec
不是关键字,解释器无法确定它是否真正执行新代码或执行其他操作...因此字节码不会更改。
例如。
>>> exec = len
>>> exec([1,2,3])
3
TL;博士
exec('...', globals())
如果您不关心添加到全局命名空间的结果,可以解决问题
完成上面的答案,以防万一。如果 exec
在某些功能中,我建议使用三参数版本如下:
def f():
d = {}
exec("def myfunc(): ...", globals(), d)
d["myfunc"]()
这是最干净的解决方案,因为它不会修改您脚下的任何命名空间。代替, myfunc
存储在显式字典中 d
。