python中的global关键字的使用方法

智者创造机会,强者把握机会,弱者坐等机会。才能是来自独创性。独创性是思维观察明白和决定的一种独特的方式。

摘要

  • global标志实际上是为了提示 python 解释器,表明被其修饰的变量是全局变量。这样解释器就可以从当前空间 (current scope) 中读写相应变量了。
  • Python 的全局变量是模块 (module) 级别的
  • 每个 python 函数拥有对应的__globals__字典,该字典与函数所属模块的__dict__字典完全相同。函数的全局变量也会从这个字典中获取
  • 注:上面三句话的意思就是,python 解释器发现函数中的某个变量被global关键字修饰,就去函数的__globals__字典变量中寻找(因为 python 中函数也是一等对象);同时,一个模块中每个函数的__globals__字典变量都是模块__dict__字典变量的引用,二者值完全相同。
  • 避免全局变量将使得程序更容易被调试,同时也能提升程序的可读性
  • 使用到的全局变量只是作为引用,不在函数中修改它的值的话,不需要加global关键字.使用到的全局变量,需要在函数中修改的话,就涉及到歧义问题. 因此在函数中修改全局变量的话需要加global关键字

动机

我最近遇到了一个关于 python 全局变量的问题,如下面这个简单例子里展示(当然实际代码要比这个复杂的多,这里只是一个抽象出来当例子)。例子中foo.py定义了函数f,而函数f调用了全局变量a:

# foo.py
 
def f():
  print(a)
 
def main():
  global a
  a = 5
  f()
 
if __name__ == '__main__':
  main()

运行上面这个文件将如预料中的输出5。在另一个文件bar.py中我们引入上面的f,代码如下

# bar.py
from foo import f
 
def main():
  f()
 
main()

运行bar.py将报NameError错误。这是因为a被定义在foo.py的main函数中,而当导入f函数时,foo.py的main函数并未被运行,所以a也没哟被定义。

Traceback (most recent call last):
 File "bar.py", line 10, in <module>
  main()
 File "bar.py", line 7, in main
  f()
 File "foo.py", line 5, in f
  print(a)
NameError: global name 'a' is not defined

定义全局变量a

为了修复上面当问题第一反应是在bar.py中定义全局变量a,这样f就可以找到变量a了,如下面的代码:

# bar.py
from foo import f
 
def main():
  global a
  a = 4
  f()
 
main()

然而依旧会报错,黑人问号脸???

Traceback (most recent call last):
 File "/tmp/example/bar.py", line 13, in <module>
  main()
 File "/tmp/example/bar.py", line 9, in main
  f()
 File "/tmp/example/foo.py", line 5, in f
  print(a)
NameError: global name 'a' is not defined

函数的__globals__属性与 python 的global语句

python 的global语句的作用只是提示 python 解释器,被global修饰的变量是一个全局变量,利用上面例子里函数f的反编译代码可以清除的看到这一点:

import dis
from foo import f
 
dis.dis(f)
5      0 LOAD_GLOBAL       0 (print)
      2 LOAD_GLOBAL       1 (a)
      4 CALL_FUNCTION      1
      6 POP_TOP
      8 LOAD_CONST        0 (None)
      10 RETURN_VALUE

面可以看出变量a被认为是全局变量。Python 中的每一个函数都拥有一个__globals__字典变量,该变量实际是函数所属模块的__dict__变量的引用。所以在bar.py中我们想在bar.main函数中将全局变量a赋值为4,实际改变的是bar.py的__dict__字典变量 (注:而不是定义f的foo.py的__dict__字典变量)

# bar.py
def main():
  global a
  a = 4
  print(main.__globals__.keys())
  print(main.__globals__['a'])
dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__annotations__', '__builtins__', '__file__', '__cached__', 'foo', 'f', 'dis', 'main', 'a'])
4

上面的代码输出了main.__globals__(即bar.__dict__) 中全局变量a的值是4,然而这个值对函数f来说确是不可见的,因为f.__globals__实际等于foo.__dict__(简单而言就是命名空间不同)

from foo import f
print(f.__globals__)

假设我们在foo.py所有函数的外部预先定义了全局变量a,那么在将函数f导入时,a会随着f.__globals__一同被导入。但这时被导入的f.__globals__["a"]( 即foo.__dict__["a"]) 和bar.main中赋值的bar.main.__globals__["a"]( 即bar.__dict__["a"]) 仍然不是同一个变量,即赋值无法改变函数f的输出,如下面的例子所示。

# foo.py
a = 3
 
def f():
  print(a)
 
def main():
  global a
  a = 5
  f()
 
if __name__ == '__main__':
  main()
# bar.py
from foo import f
 
def main():
  global a
  a = 4
  f()
 
main()

运行bar.py输出3,而不是 4。

修改函数全局变量的值:更新globals

就上述例子而言,如果我们想在bar.py中改变函数f的输出,则需要直接更新其__globals__变量的值。

# bar.py
from foo import f
 
def main():
  f.__globals__['a'] = 4
  f()
 
main()
  • 模块的dict变量和猴子布丁 (monkey-patching)

如上所述,函数的__globals__变量实际是其所属模块__dict__变量的引用。所以为了达到上面修改全局变量的目的,也可以直接更新foo.__dict__。修改模块foo的属性 (attribute) 值即可直接更新foo.__dict__。

# bar.py
import foo
from foo import f
 
 
def main():
  foo.a = 4
  f()

如果你曾经使用过运行中给代码打补丁的库,一般就是这么实现的。直接修改被打补丁的模块的__dict__中特定的对象或函数。、

输入使得函数变得更加容易测试
上面的例子中的函数f如果接受输入变量的话,而不是使用全局变量,代码将更容易被测试。同时可读性也更好,出了问题也更容易 debug。

# foo.py
def f(a):
  print(a)
 
 
def main():
  a = 5
  f(a)
 
if __name__ == '__main__':
  main()
# bar.py
from foo import f
 
def main():
  a = 3
  f(a)

到此这篇关于python中的global关键字的使用方法就介绍到这了。人生,不要给自己留下什么遗憾,用最真的微笑去面对世界的一切。更多相关python中的global关键字的使用方法内容请查看相关栏目,小编编辑不易,再次感谢大家的支持!

标签: python global