在研究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
模块不起作用)
答案 0 :(得分:2)
想象一个更一般的案例:
def f(stuff):
exec(stuff)
return x
Python显然必须在这里使用LOAD_NAME
,因为它不知道stuff
中的代码是否会混淆x
。同样的情况适用于{em>任何使用exec()
,即使参数是常量 - Python根本没有做足够深入的分析来确定任何exec()
碰巧安全。
(这也是一个非常好的理由,它也不应该进行那种分析:它甚至可以甚至分析任何成功程度的唯一情况是exec()
一个常量参数,这是没有意义的。如果参数是提前完全知道的,它应该只是普通的代码!)