这与Python如何将你的代码翻译成字节码(编译步骤)有关。
当编译一个函数时,Python 将所有被分配的变量视为局部变量,并进行优化,以减少它必须进行的名称查找次数。每个局部变量都被分配了一个索引,当函数被调用时,它们的值将被存储在一个以索引为地址的堆栈局部阵列中。编译器将发出
LOAD_FAST
和
STORE_FAST
操作码来访问该变量。
而
global
语法则向编译器表明,即使变量被赋值,也不应被视为局部变量,不应分配索引。它将使用
LOAD_GLOBAL
和
STORE_GLOBAL
操作码来访问该变量。这些操作码比较慢,因为它们使用名字在可能的许多字典(locals, globals)中进行查找。
如果一个变量只是为了读取数值而被访问,编译器总是发出
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