“global”和“import __main__”的区别
我定义了三个函数,目的是要改变一个全局变量 x
。
def changeXto1():
global x
x = 1
def changeXto2():
from __main__ import x
x = 2
def changeXto3():
import __main__
__main__.x = 3
x = 0
print x
changeXto1()
print x
changeXto2()
print x
changeXto3()
print x
结果是:
0
1
1
3
changeXto1
使用了普通的全局声明。结果如预期,x
变成了 1。changeXto2
使用 from __main__ import
来访问 x
,但这不管用。之后 x
还是 1。changeXto3
则是通过 import main
来访问 x
,结果如预期变成了 3。
为什么 from __main__ import
在 changeXto2
中不管用,而 import __main__
在 changeXto3
中却能正常工作?如果我们可以通过 __main__
模块来访问全局变量,为什么在 Python 中还需要全局声明呢?
2 个回答
为什么在
changeXto2
里from __main__ import
不管用,而在changeXto3
里import __main__
却能用呢?
其实是可以用的,只是它没有达到你想要的效果。它只是把名字和数值复制到了本地的命名空间里,而不是让代码直接访问 __main__
的命名空间。
如果我们可以通过
__main__
模块来访问全局变量,那为什么在 Python 里还需要 global 语句呢?
因为它们只有在你的代码运行在 __main__
的时候才会有相同的效果。如果你在导入了其他模块后在 othermodule
里运行代码,那么 __main__
就指的是主脚本,而不是 othermodule
。
这段内容讲的是Python是如何把你的代码转换成字节码的,也就是编译的过程。
当编译一个函数时,Python会把所有被赋值的变量当作局部变量来处理,并进行一些优化,以减少查找变量名字的次数。每个局部变量会被分配一个索引,当函数被调用时,它们的值会存储在一个由索引访问的局部数组中。编译器会发出LOAD_FAST
和STORE_FAST
这两个指令来访问这些变量。
而global
这个语法则告诉编译器,即使这个变量被赋了值,也不应该把它当作局部变量,也不分配索引。它会使用LOAD_GLOBAL
和STORE_GLOBAL
这两个指令来访问这个变量。这些指令比较慢,因为它们需要通过名字在可能有很多字典(局部变量字典和全局变量字典)中查找。
如果一个变量只是用来读取值,编译器总是会发出LOAD_GLOBAL
指令,因为它不知道这个变量应该是局部的还是全局的,所以默认认为是全局的。
所以在你的第一个函数中,使用global x
是告诉编译器,你希望把对x
的写入操作视为对全局变量的写入,而不是局部变量。函数中的指令很清楚地表明了这一点:
>>> dis.dis(changeXto1)
3 0 LOAD_CONST 1 (1)
3 STORE_GLOBAL 0 (x)
6 LOAD_CONST 0 (None)
9 RETURN_VALUE
在你的第三个例子中,你把__main__
模块导入到一个名为__main__
的局部变量中,然后给它的x
字段赋值。因为模块是存储所有顶层映射的对象,所以你实际上是在给__main__
模块中的x
变量赋值。正如你发现的,__main__
模块的字段直接映射到globals()
字典中的值,因为你的代码是在__main__
模块中定义的。指令显示你并没有直接访问x
:
>>> dis.dis(changeXto3)
2 0 LOAD_CONST 1 (-1)
3 LOAD_CONST 0 (None)
6 IMPORT_NAME 0 (__main__)
9 STORE_FAST 0 (__main__)
3 12 LOAD_CONST 2 (3)
15 LOAD_FAST 0 (__main__)
18 STORE_ATTR 1 (x)
21 LOAD_CONST 0 (None)
24 RETURN_VALUE
第二个例子很有趣。因为你给x
变量赋了值,编译器认为它是局部变量并进行了优化。然后,from __main__ import x
确实导入了__main__
模块,并在局部变量中创建了一个新的绑定,把__main__
模块中的x
的值绑定到局部变量x
。这种情况总是如此,from ${module} import ${name}
只是创建了当前命名空间中的一个新绑定。当你给x
变量赋新值时,你只是改变了当前的绑定,而不是__main__
模块中的绑定(不过如果这个值是可变的,且你修改了它,变化会在所有绑定中可见)。以下是指令:
>>> dis.dis(f2)
2 0 LOAD_CONST 1 (-1)
3 LOAD_CONST 2 (('x',))
6 IMPORT_NAME 0 (__main__)
9 IMPORT_FROM 1 (x)
12 STORE_FAST 0 (x)
15 POP_TOP
3 16 LOAD_CONST 3 (2)
19 STORE_FAST 0 (x)
22 LOAD_CONST 0 (None)
25 RETURN_VALUE
可以这样理解,在Python中,所有的赋值都是把一个名字绑定到字典中的一个值,而解除引用就是在字典中查找(这只是一个粗略的近似,但接近概念模型)。当你使用obj.field
时,你实际上是在查找obj
的隐藏字典(可以通过obj.__dict__
访问)中的"field"
这个键。
当你有一个裸变量名时,它会在locals()
字典中查找,如果在这个字典中找不到,就会在globals()
字典中查找(在模块级别执行代码时,这两个字典是相同的)。对于赋值操作,它总是把绑定放在locals()
字典中,除非你通过global ${name}
声明你想要全局访问(这种语法在顶层也适用)。
所以翻译你的函数,几乎就像你写了:
# NOTE: this is valid Python code, but is less optimal than
# the original code. It is here only for demonstration.
def changeXto1():
globals()['x'] = 1
def changeXto2():
locals()['x'] = __import__('__main__').__dict__['x']
locals()['x'] = 2
def changeXto3():
locals()['__main__'] = __import__('__main__')
locals()['__main__'].__dict__['x'] = 3