Jed*_*urt 11 python metaprogramming exec backwards-compatibility python-3.x
我在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()
Run Code Online (Sandbox Code Playgroud)
# 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()
Run Code Online (Sandbox Code Playgroud)
由于标准函数定义适用于两个Python版本,我假设问题必须是对exec工作方式的改变.我阅读了2.6和3的API文档,并exec阅读了"Python 3.0中的新功能"页面,并且看不出代码中断的任何原因.
JBe*_*rdo 10
您可以看到每个Python版本生成的字节码:
>>> from dis import dis
Run Code Online (Sandbox Code Playgroud)
并且,对于每个翻译:
#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)
...
Run Code Online (Sandbox Code Playgroud)
如您所见,Python 3.2搜索名为的全局值(LOAD_GLOBAL)a_func,2.7在搜索全局范围之前首先搜索本地范围(LOAD_NAME).
如果你这样做print(locals())后exec,你会看到a_func在内部创建__init__功能.
我真的不知道为什么会这样做,但似乎是对符号表处理方式的改变.
顺便说一句,如果想要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
Run Code Online (Sandbox Code Playgroud)
TL;博士
exec('...', globals()) 如果你不关心将结果添加到全局命名空间,可以解决问题
完成上面的答案,以防万一.如果exec是某个函数,我建议使用三参数版本,如下所示:
def f():
d = {}
exec("def myfunc(): ...", globals(), d)
d["myfunc"]()
Run Code Online (Sandbox Code Playgroud)
这是最干净的解决方案,因为它不会修改您脚下的任何命名空间.相反,myfunc存储在显式字典中d.