“global”和“import __main__”的区别

7 投票
2 回答
4030 浏览
提问于 2025-04-17 16:05

我定义了三个函数,目的是要改变一个全局变量 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__ importchangeXto2 中不管用,而 import __main__changeXto3 中却能正常工作?如果我们可以通过 __main__ 模块来访问全局变量,为什么在 Python 中还需要全局声明呢?

2 个回答

8

为什么在 changeXto2from __main__ import 不管用,而在 changeXto3import __main__ 却能用呢?

其实是可以用的,只是它没有达到你想要的效果。它只是把名字和数值复制到了本地的命名空间里,而不是让代码直接访问 __main__ 的命名空间。

如果我们可以通过 __main__ 模块来访问全局变量,那为什么在 Python 里还需要 global 语句呢?

因为它们只有在你的代码运行在 __main__ 的时候才会有相同的效果。如果你在导入了其他模块后在 othermodule 里运行代码,那么 __main__ 就指的是主脚本,而不是 othermodule

10

这段内容讲的是Python是如何把你的代码转换成字节码的,也就是编译的过程。

当编译一个函数时,Python会把所有被赋值的变量当作局部变量来处理,并进行一些优化,以减少查找变量名字的次数。每个局部变量会被分配一个索引,当函数被调用时,它们的值会存储在一个由索引访问的局部数组中。编译器会发出LOAD_FASTSTORE_FAST这两个指令来访问这些变量。

global这个语法则告诉编译器,即使这个变量被赋了值,也不应该把它当作局部变量,也不分配索引。它会使用LOAD_GLOBALSTORE_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

撰写回答