摘要
- 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关键字的使用方法内容请查看相关栏目,小编编辑不易,再次感谢大家的支持!