Python internals - How do objects know about global variables?

安稳与你 提交于 2019-12-09 20:00:45

问题


I recently discovered some interesting behavior that left me wondering about how an object knows about which global variables exist. For example, suppose I have a file "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

Then in an interactive shell I do this:

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

Nothing surprising. Then I change the first few lines of "test.py" to this:

globalVar = 4 
toCreate = 5

class Test(object):
    classVar = 6

Now back to the interactive 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']

So tester now knows about toCreate, which showed up after tester itself was created. It still knows about toDelete because reloading a module apparently doesn't affect a global variable that was deleted. Here comes a twist:

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

Deleting the module from sys.modules and then reimporting the module results in all the global variables becoming None.

Also of interest is that if I delete test and sys.modules['test'], it still knows about the values of the variables for a while. After a little while (which I assume is how long it takes for the module to be garbage collected) the values become None. Reimporting the module causes the garbage collection (or whatever is going on) to happen immediately.

So how does tester find out about a new global variable being created, and then once the module is gone why does it still know which variables existed even though it no longer knows what values they held?


回答1:


Any name that is not a local (has not been assigned to in the current scope) is instead assumed to be a global. The name is looked up every time the code runs.

So at runtime the name is looked up in the global namespace, which is just a dictionary. If the name doesn't exist at that time, a NameError exception is raised.

You can see this when you disassemble a function; bytecode is shown when using the 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 is a local (it was assigned to in the block), while baz is a global. The local is referenced by LOAD_FAST, while the global is referenced by LOAD_GLOBAL.

To do this, function objects have a function.__globals__ reference linking it to the module globals mapping; see the User-defined functions section in the datamodel documentation:

>>> foo.__globals__ is globals()
True

Python also cleans up globals when deleting modules; to prevent circular references holding up finalisation globals are rebound to None at that time (although this behaviour changed in Python 3.4). If you however maintain a reference to tester, your code will find those None values.

Your tester instance, still referencing the original class and it's methods, are still referencing their module through their function.__globals__ references. So, although you deleted the sys.modules reference to the module, triggering the module cleanup, the globals dictionary is still referenced by the class methods. This globals dictionary now holds None values for each global.



来源:https://stackoverflow.com/questions/24020108/python-internals-how-do-objects-know-about-global-variables

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!