【问题标题】:Python internals - How do objects know about global variables?Python 内部 - 对象如何知道全局变量?
【发布时间】:2014-07-24 01:55:59
【问题描述】:

我最近发现了一些有趣的行为,这让我想知道对象如何知道存在哪些全局变量。例如,假设我有一个文件“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

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

>>> 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

没什么好奇怪的。然后我把“test.py”的前几行改成这样:

globalVar = 4 
toCreate = 5

class Test(object):
    classVar = 6

现在回到交互式外壳:

>>> 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']

所以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']

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

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

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

【问题讨论】:

  • 对象对范围一无所知。 功能做。
  • @delnan 函数是对象,所以... *ducks and runs*

标签: python python-2.7 global-variables python-import python-module


【解决方案1】:

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

所以在 runtime 中会在全局命名空间中查找名称,这只是一个字典。如果该名称当时不存在,则会引发 NameError 异常。

反汇编函数时可以看到;使用dis module时显示字节码:

>>> 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        

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

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

>>> foo.__globals__ is globals()
True

Python 在删除模块时也清理全局变量;为防止循环引用阻碍最终确定,当时全局变量被反弹到None(尽管这种行为changed in Python 3.4)。但是,如果您保留对 tester 的引用,您的代码将找到那些 None 值。

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

【讨论】:

  • 那么为什么我得到变量的None 而不是NameErrortoDelete 不再存在,但 tester 仍然知道它存在。
  • @RobWatts 在 Python 2 中,当模块被删除时,其所有全局变量都设置为 None。现在已修复:legacy.python.org/dev/peps/pep-0442
  • @Martijn 我认为解释 如何 函数获取它们的全局变量 (f.__globals__)、该 dict 的来源以及模块在使用时会发生什么情况会很有用垃圾收集(清除其__dict__)。 (我现在没有时间写一个有竞争力的答案。)
  • 我不知道如何清理全局变量。我相信这就是这里发生的事情。
  • @delnan 是的;我也受到时间的限制(在其他地方分心)。
猜你喜欢
  • 2013-05-06
  • 2022-08-02
  • 2019-07-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-30
  • 1970-01-01
  • 2017-03-13
相关资源
最近更新 更多