我定义了三个应该改变全局变量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
解决__main__.x
问题。之后的结果是预期的3。
为什么from __main__ import
不在changeXto2
中工作,而import __main__
在changeXto3
工作?如果我们可以使用__main__
模块来解决全局变量,为什么我们需要Python中的全局语句?
答案 0 :(得分:10)
这与Python如何将代码转换为字节码(编译步骤)有关。
编译函数时,Python会处理所有分配为局部变量的变量,并执行优化以减少必须执行的名称查找次数。每个局部变量都被赋予一个索引,当调用该函数时,它们的值将存储在由index寻址的堆栈本地数组中。编译器将发出LOAD_FAST
和STORE_FAST
操作码来访问变量。
global
语法指示编译器即使为变量赋值,也不应将其视为局部变量,不应为其分配索引。它将使用LOAD_GLOBAL
和STORE_GLOBAL
操作码来访问变量。这些操作码较慢,因为它们使用该名称在可能的许多字典(本地,全局)中进行查找。
如果仅访问变量以读取值,则编译器始终发出LOAD_GLOBAL
,因为它不知道它应该是本地变量还是全局变量,因此假设它是全局变量。 / p>
因此,在您的第一个函数中,使用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
字段。由于module是将所有顶级映射存储为字段的对象,因此您将分配给x
模块中的变量__main__
。正如您所发现的,__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__
,并在模块x
中创建一个__main__
值的新绑定到名为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
答案 1 :(得分:8)
为什么
from __main__ import
无效changeXto2
,import __main__
changeXto3
正在__main__
工作?
它工作正常,它不会做你想要的。它将名称和值复制到本地名称空间,而不是让代码访问__main__
的名称空间。
如果我们可以使用
__main__
模块解决全局变量,为什么我们需要Python中的全局语句?
因为当代码在othermodule
中运行时,它们只会执行相同的操作。如果您在导入后运行,__main__
,则othermodule
将引用主脚本而不 {{1}}。