Python exec() 中的全局和局部变量

41 投票
4 回答
46307 浏览
提问于 2025-04-15 23:08

我正在尝试用exec运行一段Python代码。

my_code = """
class A(object):
  pass

print 'locals: %s' % locals()
print 'A: %s' % A

class B(object):
  a_ref = A
"""

global_env = {}
local_env = {}
my_code_AST = compile(my_code, "My Code", "exec")
exec(my_code_AST, global_env, local_env)

print local_env

结果输出如下:

locals: {'A': <class 'A'>}
A: <class 'A'>
Traceback (most recent call last):
  File "python_test.py", line 16, in <module>
    exec(my_code_AST, global_env, local_env)
  File "My Code", line 8, in <module>
  File "My Code", line 9, in B
NameError: name 'A' is not defined

但是,如果我把代码改成这样 -

my_code = """
class A(object):
  pass

print 'locals: %s' % locals()
print 'A: %s' % A

class B(A):
  pass
"""

global_env = {}
local_env = {}
my_code_AST = compile(my_code, "My Code", "exec")
exec(my_code_AST, global_env, local_env)

print local_env

那么它就能正常工作,输出如下 -

locals: {'A': <class 'A'>}
A: <class 'A'>
{'A': <class 'A'>, 'B': <class 'B'>}

显然,A是存在的并且可以访问 - 那么第一段代码出了什么问题呢?我用的是2.6.5版本,感谢!

Colin

* 更新 1 *

如果我在类里面检查locals() -

my_code = """
class A(object):
  pass

print 'locals: %s' % locals()
print 'A: %s' % A

class B(object):
  print locals()
  a_ref = A
"""

global_env = {}
local_env = {}
my_code_AST = compile(my_code, "My Code", "exec")
exec(my_code_AST, global_env, local_env)

print local_env

那么就清楚了,两个地方的locals()是不一样的 -

locals: {'A': <class 'A'>}
A: <class 'A'>
{'__module__': '__builtin__'}
Traceback (most recent call last):
  File "python_test.py", line 16, in <module>
    exec(my_code_AST, global_env, local_env)
  File "My Code", line 8, in <module>
  File "My Code", line 10, in B
NameError: name 'A' is not defined

但是,如果我这样做,就没有问题了 -

def f():
  class A(object):
    pass

  class B(object):
    a_ref = A

f()

print 'Finished OK'

* 更新 2 *

好的,这里有文档 - http://docs.python.org/reference/executionmodel.html

'类定义是一个可执行的语句,可以使用和定义名称。这些引用遵循正常的名称解析规则。类定义的命名空间成为类的属性字典。在类作用域定义的名称在方法中不可见。'

我觉得'A'应该在B的定义这个可执行语句中作为自由变量可用,而这在我们调用f()时发生,但在使用exec()时却没有。这可以通过以下方式更容易地展示 -

my_code = """
class A(object):
  pass

print 'locals in body: %s' % locals()
print 'A: %s' % A

def f():
  print 'A in f: %s' % A

f()

class B(object):
  a_ref = A
"""

输出为:

locals in body: {'A': <class 'A'>}
A: <class 'A'>
Traceback (most recent call last):
  File "python_test.py", line 20, in <module>
    exec(my_code_AST, global_env, local_env)
  File "My Code", line 11, in <module>
  File "My Code", line 9, in f
NameError: global name 'A' is not defined

所以我想新的问题是 - 为什么这些locals没有作为自由变量在函数和类定义中暴露出来 - 这看起来像是一个相当标准的闭包场景。

4 个回答

3

如果你在问怎么让 exec 这个语句像文件作用域那样工作,我参考了链接中的一些提示和问题,发现只需要传递一个字典给全局变量和局部变量就可以了。显然,文件作用域是一个特殊的情况,在这里局部声明会自动放到全局作用域中。

exec code in dict()
10

在你执行 print locals()print globals() 之后,你会发现为什么 exec 会抛出“未定义”的异常。你可以试试看这个。

d = dict(locals(), **globals())
exec (code, d, d)
20

我觉得这可能是一个实现上的错误,或者是一个没有文档说明的设计决定。问题的关键在于,在模块范围内进行名称绑定操作时,应该绑定到一个全局变量。实现这个的方式是,当你在模块级别时,globals()和locals()是一样的(你可以在解释器里试试这个)。所以当你进行任何名称绑定时,它会像往常一样把它分配给locals()字典,而这个字典也是globals,因此就创建了一个全局变量。

当你查找一个变量时,首先会检查你当前的局部变量,如果找不到这个名字,就会递归地检查包含的作用域中的局部变量,直到找到这个变量或者到达模块范围。如果到达模块范围,就会检查全局变量,这些全局变量本应该是模块范围的局部变量。

>>> exec(compile("import sys\nprint sys._getframe().f_code.co_name", "blah", "exec"), {}, {})
<module>
>>> exec("a = 1\nclass A(object):\n\tprint a\n", {}, {})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 2, in <module>
  File "<string>", line 3, in A
NameError: name 'a' is not defined
>>> d = {}
>>> exec("a = 1\nclass A(object):\n\tprint a\n", d,d)
1

这种行为就是为什么继承能够正常工作的原因(名称查找使用了代码对象的局部变量,而这个局部变量确实包含了A)。

总的来说,这在CPython的实现中是一个不太优雅的解决办法,特别处理了全局变量的查找。这也导致了一些不合逻辑的情况,比如:

>>> def f():
...     global a
...     a = 1
...
>>> f()
>>> 'a' in locals()
True

请注意,这些都是我在阅读Python语言参考的第4.1节(命名和绑定)时,玩弄解释器得出的推测。虽然这不是绝对的(我没有查看CPython的源代码),但我相当确定我对这种行为的理解是正确的。

撰写回答