"global "和 "import __main__"之间的区别

7 人关注

我定义了三个函数,它们应该改变一个全局变量 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

It gives the result:

changeXto1使用正常的全局语句。结果正如预期的那样 x == 1。changeXto2使用from __main__ import来处理x。这并不奏效。之后,x仍然是1。changeXto3使用import main通过__main__.x来寻址x。之后的结果如预期的那样是3。

为什么from __main__ importchangeXto2中不工作,而import __main__却在changeXto3中工作?如果我们也可以用__main__模块来处理全局变量,为什么我们还需要Python中的全局语句?

2 个评论
我没有看到任何关于导入模块importing的内容。
python
Holger
Holger
发布于 2013-02-16
2 个回答
Sylvain Defresne
Sylvain Defresne
发布于 2013-02-16
已采纳
0 人赞同

这与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
    
谢谢你这个非常全面的回答。我一直认为 import module from module import 是一样的,只是变量的调用方式不同。我看到,当使用 from import 创建新的变量时,是有区别的。原始模块中的变量保持不变,但本地命名空间中的变量是新创建的,并与本地名称绑定。
Ignacio Vazquez-Abrams
Ignacio Vazquez-Abrams
发布于 2013-02-16
0 人赞同

Why doesn't from __main__ import work in changeXto2 , while import __main__ is working in changeXto3 ?

它可以正常工作,只是没有做你想要的事情。它将名称和值复制到本地命名空间,而不是让代码访问 __main__ 的命名空间。