exec语句将LOAD_GLOBAL更改为LOAD_NAME?

时间:2013-09-06 00:05:39

标签: python

在研究this answer时,我惊讶地发现,exec有一种奇怪的行为;

>>> def f1():
...     return x
... 
>>> def f2():
...     exec ""
...     return x
... 
>>> f1()
Traceback (most recent call last):
  ...
NameError: global name 'x' is not defined
>>> f2()
Traceback (most recent call last):
  ...
NameError: name 'x' is not defined
>>> x = 'bar'
>>> f1()
'bar'
>>> f2()
'bar'

显然,两者都返回一些全局值x;但如果f2()略有改变,则不是这样:

>>> def f2():
...     exec "x = 'im local now'"
...     return x
... 
>>> f2()
'im local now'

f2返回自己的x特殊副本,即使f2正文中没有任何内容可能会导致这种情况(没有赋值给x)。

我可以很容易地看到这是如何发生的,exec语句的存在将LOAD_GLOBAL字节码更改为LOAD_NAME,与yield的存在类似将函数转换为生成器。

>>> dis.dis(f1)
  2           0 LOAD_GLOBAL              0 (x)
              3 RETURN_VALUE        
>>> dis.dis(f2)
  2           0 LOAD_CONST               1 ('')
              3 LOAD_CONST               0 (None)
              6 DUP_TOP             
              7 EXEC_STMT           

  3           8 LOAD_NAME                0 (x)
             11 RETURN_VALUE        

但我不明白的是为什么。这是记录在案的行为?这是cpython的实现细节吗? (它在IronPython中以相同的方式工作,尽管dis模块不起作用)

1 个答案:

答案 0 :(得分:2)

想象一个更一般的案例:

def f(stuff):
    exec(stuff)
    return x

Python显然必须在这里使用LOAD_NAME,因为它不知道stuff中的代码是否会混淆x。同样的情况适用于{em>任何使用exec(),即使参数是常量 - Python根本没有做足够深入的分析来确定任何exec()碰巧安全。

(这也是一个非常好的理由,它也不应该进行那种分析:它甚至可以甚至分析任何成功程度的唯一情况是exec()一个常量参数,这是没有意义的。如果参数是提前完全知道的,它应该只是普通的代码!)