2

在研究这个答案时,令我惊讶的是,我发现它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字节码更改为 a 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模块不起作用)

4

1 回答 1

2

想象一个更一般的情况:

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

Python 显然必须在LOAD_NAME这里使用,因为它不知道里面的代码stuff会不会和x. 相同的情况适用于 的任何使用exec(),即使参数是一个常量——Python 根本没有进行足够深入的分析来确定任何exec()碰巧是安全的。

(它也不应该进行这种分析也有一个很好的理由:它甚至可以成功分析的唯一情况是不断争论exec(),这是毫无意义的。如果提前完全了解争论,它应该只是普通代码!)

于 2013-09-06T00:12:33.517 回答