Python内部 - 对象如何知道全局变量?

Rob*_*tts 2 python python-module global-variables python-import python-2.7

我最近发现了一些有趣的行为让我想知道对象如何知道存在哪些全局变量.例如,假设我有一个文件"test.py":

globalVar = 1
toDelete = 2

class Test(object):
    classVar = 3

    def runTest1(self):
        print globalVar
        print toDelete
        print missingVar

    def runTest2(self):
        print self.classVar
        print toCreate
        print missingVar
Run Code Online (Sandbox Code Playgroud)

然后在交互式shell中我这样做:

>>> import test
>>> tester = test.Test()
>>> tester.runTest1()
1
2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "test.py", line 10, in runTest1
    print missingVar
NameError: global name 'missingVar' is not defined
>>> tester.runTest2()
3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "test.py", line 14, in runTest2
    print toCreate
NameError: global name 'toCreate' is not defined
Run Code Online (Sandbox Code Playgroud)

没什么好惊讶的.然后我将"test.py"的前几行更改为:

globalVar = 4 
toCreate = 5

class Test(object):
    classVar = 6
Run Code Online (Sandbox Code Playgroud)

现在回到交互式shell:

>>> reload(test) # test = reload(test) gives the same result 
<module 'test' from 'test.py'>
>>> tester.runTest1()
4
2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "test.py", line 10, in runTest1
    print missingVar
NameError: global name 'missingVar' is not defined
>>> tester.runTest2()
3
5
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "test.py", line 15, in runTest2
    print missingVar
NameError: global name 'missingVar' is not defined
>>> dir(test)
['Test', '__builtins__', '__doc__', '__file__', '__name__', '__package__', 'globalVar', 'toCreate', 'toDelete']
Run Code Online (Sandbox Code Playgroud)

所以tester现在知道了toCreate,它tester是在创建之后出现的.它仍然知道,toDelete因为重新加载模块显然不会影响已删除的全局变量.这有一个转折点:

>>> import sys
>>> import importlib
>>> del(sys.modules['test']) # remove cached version
>>> test = importlib.import_module('test') # same result if I don't have 'test = '
>>> tester.runTest1()
None
None
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "test.py", line 10, in runTest1
    print missingVar
NameError: global name 'missingVar' is not defined
>>> tester.runTest2()
3
None
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "test.py", line 15, in runTest2
    print missingVar
NameError: global name 'missingVar' is not defined
>>> dir(test)
['Test', '__builtins__', '__doc__', '__file__', '__name__', '__package__', 'globalVar', 'toCreate']
Run Code Online (Sandbox Code Playgroud)

从模块中删除模块sys.modules然后重新导入模块会导致所有全局变量变为None.

同样感兴趣的是,如果我删除testsys.modules['test'],它仍然知道了一段时间的变量的值.过了一会儿(我假设模块被垃圾收集需要多长时间),这些值就变成了None.重新导入模块会导致垃圾收集(或正在发生的任何事情)立即发生.

那么如何tester找到一个新的全局变量被创建,然后一旦模块消失,为什么它仍然知道哪些变量存在,即使它不再知道它们持有什么值?

Mar*_*ers 5

任何非本地的名称(尚未在当前范围内分配)都被假定为全局名称.每次代码运行时都会查找该名称.

因此,在运行时,名称将在全局命名空间中查找,该命名空间只是一个字典.如果此时名称不存在,NameError则会引发异常.

你可以在反汇编函数时看到这个; 使用dis模块时显示字节码:

>>> import dis
>>> def foo():
...     bar = 'baz'  # local
...     bar  # reference the local
...     baz  # reference something else; e.g. a global
... 
>>> dis.dis(foo)
  2           0 LOAD_CONST               1 ('baz')
              3 STORE_FAST               0 (bar)

  3           6 LOAD_FAST                0 (bar)
              9 POP_TOP             

  4          10 LOAD_GLOBAL              0 (baz)
             13 POP_TOP             
             14 LOAD_CONST               0 (None)
             17 RETURN_VALUE        
Run Code Online (Sandbox Code Playgroud)

bar是一个本地(它被分配到块中),baz而是一个全球性的.本地引用LOAD_FAST,而全局引用LOAD_GLOBAL.

为此,函数对象具有function.__globals__将其链接到模块全局映射的引用; 请参阅datamodel文档中用户定义函数部分:

>>> foo.__globals__ is globals()
True
Run Code Online (Sandbox Code Playgroud)

Python 在删除模块时也会清除全局变量 ; 防止循环引用阻止终结全局变量None在那时被反弹(虽然这种行为在Python 3.4中有所改变).但是,如果您保留对引用的引用tester,则代码将找到这些None值.

您的tester实例仍然引用原始类及其方法,仍然通过function.__globals__引用引用其模块.因此,虽然您删除了sys.modules对模块的引用,但触发了模块清理,但类方法仍然引用了globals字典.这个全局字典现在包含None每个全局的值.