11

我查看了SO上的无数'Python exec'线程,但找不到解决我的问题的线程。非常抱歉,如果这之前已经问过。这里是我的问题:为什么Python 3更改为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作品。我读了API文档的2.6和3 exec和也可以参考“什么是新的Python 3.0”页面,看不出有任何理由代码将打破。

+5

这似乎是很可能使任何人必须在5年内维持它的代码。 – Amber

+4

我认为这是一个比实际使用的代码更包含的例子。我听说'exec'和'eval'在语言中占有一席之地。 –

+3

@Amber也许我是个虐待狂? –

回答

10

你可以看到生成的字节码为每个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)。

如果您在exec之后做print(locals()),您会看到在__init__函数内部创建了a_func

我真的不知道为什么它这样做的方式,但似乎对symbol tables是如何处理的变化。

顺便说一句,如果想在你的__init__方法做出解释知道这是一个局部变量的顶部创建一个a_func = None,它会不工作,因为字节码现在将LOAD_FAST和不进行搜索,而是直接从列表中获取值。

我看到的唯一的解决办法是作为第二个参数添加到globals()exec,这样会造成a_func作为一个全球性的功能可以由LOAD_GLOBAL码来访问。

编辑

如果删除exec声明,Python2.7改变字节码从LOAD_NAMELOAD_GLOBAL。因此,使用exec,您的代码在Python2.x上总是比较慢,因为它必须搜索本地作用域以进行更改。

由于Python3的exec不是关键字,因此解释器无法确定它是真的执行新代码还是执行其他操作......所以字节码不会改变。

E.g.

>>> exec = len 
>>> exec([1,2,3]) 
3 

TL;博士

exec('...', globals())可以解决这个问题,如果你不小心被加入到全局命名空间

+0

它的工作。很好的答案;字节码内省真的很酷。不知道这一点。感谢分享。 –

+0

@Jedidiah Hurt添加了更多信息。 – JBernardo

+1

由于'exec'正在考虑本地作用域,并且作为'locals()'存储它,所以还可以执行exec(“a_func()”)或locals()['a_func']()'。 – MGwynne

5

结果完成上述答案,以防万一。如果exec是在一些功能,我会建议使用三参数版本如下:

def f(): 
    d = {} 
    exec("def myfunc(): ...", globals(), d) 
    d["myfunc"]() 

这是干净的解决方案,因为它不会改变你脚下的任何命名空间。相反,myfunc存储在显式词典d中。

+0

我喜欢这样,感觉对我更安全:) – chisaipete

相关问题