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


Posted in Python onAugust 20, 2019

摘要

  • 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 相关文章推荐
python thread 并发且顺序运行示例
Apr 09 Python
Python的函数嵌套的使用方法
Jan 24 Python
Python PyQt5实现的简易计算器功能示例
Aug 23 Python
python for循环输入一个矩阵的实例
Nov 14 Python
如何用python写一个简单的词法分析器
Dec 18 Python
在PYQT5中QscrollArea(滚动条)的使用方法
Jun 14 Python
PyQT5 QTableView显示绑定数据的实例详解
Jun 25 Python
Pandas之DataFrame对象的列和索引之间的转化
Jun 25 Python
Laravel框架表单验证格式化输出的方法
Sep 25 Python
python GUI库图形界面开发之PyQt5滑块条控件QSlider详细使用方法与实例
Feb 28 Python
PyTorch中的C++扩展实现
Apr 02 Python
Python利用Xpath选择器爬取京东网商品信息
Jun 01 Python
python并发编程 Process对象的其他属性方法join方法详解
Aug 20 #Python
浅谈pytorch grad_fn以及权重梯度不更新的问题
Aug 20 #Python
解决Pytorch 训练与测试时爆显存(out of memory)的问题
Aug 20 #Python
python中用logging实现日志滚动和过期日志删除功能
Aug 20 #Python
python3中替换python2中cmp函数的实现
Aug 20 #Python
python 并发编程 多路复用IO模型详解
Aug 20 #Python
关于pytorch中网络loss传播和参数更新的理解
Aug 20 #Python
You might like
使用PHP备份MYSQL数据的多种方法
2014/01/15 PHP
Zend Framework 2.0事件管理器(The EventManager)入门教程
2014/08/11 PHP
Laravel使用原生sql语句并调用的方法
2019/10/09 PHP
HTML Color Picker(js拾色器效果)
2013/08/27 Javascript
js中cookie的添加、取值、删除示例代码
2013/10/21 Javascript
js生成的验证码的实现与技术分析
2014/09/17 Javascript
Web表单提交之disabled问题js解决方法
2015/01/13 Javascript
javascript比较两个日期相差天数的方法
2015/07/23 Javascript
JavaScript中利用各种循环进行遍历的方式总结
2015/11/10 Javascript
Javascript的表单验证-揭开正则表达式的面纱
2016/03/18 Javascript
深入理解AngularJS中的ng-bind-html指令和$sce服务
2016/09/08 Javascript
javascript实现下雨效果
2017/03/27 Javascript
js实现多行文本框统计剩余字数功能
2017/03/28 Javascript
Vue单文件组件基础模板小结
2017/08/10 Javascript
深入理解Vue.js源码之事件机制
2017/09/27 Javascript
面试题:react和vue的区别分析
2019/04/08 Javascript
浅谈一种让小程序支持JSX语法的新思路
2019/06/16 Javascript
微信小程序Echarts图表组件使用方法详解
2019/06/25 Javascript
JavaScript实现公告栏上下滚动效果
2020/03/13 Javascript
详解python中@的用法
2019/03/27 Python
Python:type、object、class与内置类型实例
2019/12/25 Python
TensorFlow:将ckpt文件固化成pb文件教程
2020/02/11 Python
Python爬虫爬取百度搜索内容代码实例
2020/06/05 Python
Omio俄罗斯:一次搜索公共汽车、火车和飞机的机票
2018/11/17 全球购物
在对linux系统分区进行格式化时需要对磁盘簇(或i节点密度)的大小进行选择,请说明选择的原则
2012/11/24 面试题
美发店5.1活动方案
2014/01/24 职场文书
大学生自我鉴定评语
2014/01/27 职场文书
《石榴》教学反思
2014/03/02 职场文书
2014年乡镇个人工作总结
2014/12/03 职场文书
2015年优质护理服务工作总结
2015/04/08 职场文书
面试通知单大全
2015/04/20 职场文书
人事任命通知书
2015/04/21 职场文书
贷款收入证明格式
2015/06/24 职场文书
php随机生成验证码,php随机生成数字,php随机生成数字加字母!
2021/04/01 PHP
Python中非常使用的6种基本变量的操作与技巧
2022/03/22 Python
mysql查询结果实现多列拼接查询
2022/04/03 MySQL