Python为什么引入这两个关键词:global和nonlocal

开发 后端
本文通过分析函数对全局变量和闭包变量的读、写操作,借助于反汇编字节码分析,认清了 global 和 nonlocal 关键字的用法,对其引入和作用有了较为深刻认识。

[[408478]]

本文转载自微信公众号「Python中文社区」,作者巩庆奎。转载本文请联系Python中文社区公众号。

啥是 global 和 nonlocal

Python 支持的关键词里,global 和 nonlocal 初学者接触的少,不知道是做什么用的;一些人虽然知道它们的作用,但对为什么要引入这两个关键词则有些不知其所以然。

粗浅地说,global 和 nonlocal 是为了在函数中修改全局和闭包变量而引入的关键字。

本文用代码一点点分析引入 global 和 nonlocal 的原因。

一个奇怪的现象

下面,让我们做一个测试。

  1. g =1 
  2. def fun(): 
  3.     g = 2 
  4.     return g  
  5. print(fun(),g) 

一般地,我们认为结果应该为 2,2。这一点学过其他语言如 Java、c 的同学尤其认同。

但让我们跑起来,可以看到结果为 2,1。也就是说,函数没有改变全局变量 g。

这很奇怪,究其原因是因为:

  • Python 认为所有 = 赋值都是在当前作用域新建变量。
    • 当我们在程序中 g = 1 时,表示当前全局作用域建立 g,赋值 1。
    • 当我们在函数中 g = 2 时,表示当前局部作用域建立 g,赋值 2。

使用 dis.dis(fun) 分析 fun 函数源代码:

  1. 16           0 LOAD_CONST               1 (2) 
  2.               2 STORE_FAST               0 (g) 
  3.  17           4 LOAD_FAST                0 (g) 
  4.               6 RETURN_VALUE 

可见,第 2 条指令 STORE_FAST,这是存储到局部变量的命令。

所以,函数中实际操作的是局部变量。

还有更甚的例子如下,大家猜测下执行结果。

  1. g =1 
  2. def fun(): 
  3.     g += 1 
  4.     return g 
  5. print(fun(),g)  

根据上文,我们知道函数不会改变全局变量 g,那么结果应该是 2,1,这次总算对了吧?

很抱歉,当执行到 g += 1 时,系统报错:UnboundLocalError: local variable 'g' referenced before assignment。

仔细观察错误,local variable 'g',这里的 g 仍然被视为局部变量:没有定义(=赋值),就直接 inplace add,当然要报错。

也就是说,所有在局部作用域中对全局变量的赋值、原位赋值都会失败。唯有如下函数给我们带来一丝安慰。

  1. g =1 
  2. def fun(): 
  3.     return g 
  4. print(fun(),g)  

结果 1,1,总算还有个正常的:在局部作用域中引用全局作用域变量正常。

那当我必须修改全局变量时,该怎么办呢?

global 的引入和分析

这就是 global 引入的理由了,将全局变量扩展到函数中来,使函数可以修改全局变量。

  1. g =1 
  2. def fun(): 
  3.     global g 
  4.     g = 2 
  5.     return g 
  6. print(fun(),g)  

结果为 2,2,函数修改了全局变量。我们来看 dis.dis(fun) 的反汇编代码。

  1. 37           0 LOAD_CONST               1 (2) 
  2.              2 STORE_GLOBAL             0 (g) 
  3. 38           4 LOAD_GLOBAL              0 (g) 
  4.              6 RETURN_VALUE 

第 2 条指令,STORE_GLOBAL 是将常量 2 赋值给全局变量 g,异于上例中的 STORE_FAST指令对局部变量操作。

故此,我们得出结论:当在函数中读取全局变量时,可以直接使用。但如果需要修改全局变量值,则需要在变量前加上 global 来修饰。

nonlocal 的引入

同样地,当我们书写嵌套函数,需要对闭包中的变量进行修改操作时,我们也需要引入 nonlocal 关键字。

如下函数中,我们定义了闭包,闭包中的变量 e,试图在内嵌函数中进行修改,但没有使用 nonlocal 关键字声明 e。

  1. def outer(): 
  2.     e = 1 
  3.     def inner(): 
  4.         e = 2 
  5.         return e 
  6.     return inner 

参照上例,我们知道这种修改是徒劳的——因为看反汇编代码 dis.dis(outer()) 可知:

  1. 63           0 LOAD_CONST               1 (2) 
  2.              2 STORE_FAST               0 (e) 
  3. 64           4 LOAD_FAST                0 (e) 
  4.              6 RETURN_VALUE 

第 2 条指令 STORE_FAST,操作局部变量,也就是说 inner 里的 e,仍然被视为局部变量。

雷同于上例的 global,这里使用 nonlocal 来在内嵌函数 inner 中修改闭包变量 e。

  1. def outer(): 
  2.     e = 1 
  3.     def inner(): 
  4.         nonlocal e 
  5.         e = 2 
  6.         return e 
  7.     return inner 

查看此时的反汇编代码 dis.dis(outer()) 可知:

  1. 78           0 LOAD_CONST               1 (2) 
  2.              2 STORE_DEREF              0 (e) 
  3. 79           4 LOAD_DEREF               0 (e) 
  4.              6 RETURN_VALUE 

第 2 条指令 STORE_DEREF,操作的是闭包变量,也就是说 inner 里的 e,是可以修改的闭包中的 e。

总结

本文通过分析函数对全局变量和闭包变量的读、写操作,借助于反汇编字节码分析,认清了 global 和 nonlocal 关键字的用法,对其引入和作用有了较为深刻认识。

 

作者:巩庆奎,大奎,对计算机、电子信息工程感兴趣。

 

责任编辑:武晓燕 来源: Python中文社区
相关推荐

2011-06-20 14:32:59

关键词

2011-06-07 18:45:41

关键词

2011-06-14 19:11:38

关键词

2016-06-02 11:02:02

机器学习

2011-06-21 16:11:04

SEO关键词

2011-06-16 17:54:25

关键词

2013-08-26 15:43:40

AppStore关键词开发者应用选取关键词

2011-05-25 17:38:56

关键词

2019-12-22 13:48:26

退休科技行业大佬

2011-05-25 17:58:00

2023-01-13 16:57:50

SpringBoot配置核心

2020-12-10 10:32:33

区块链比特币数字货币

2013-04-16 21:52:27

IBM云计算开源

2011-05-10 10:39:07

关键词SEO

2016-01-06 10:05:20

2011-06-02 18:12:35

2018-02-08 17:20:47

2011-07-22 15:48:46

SEO

2014-09-12 14:03:45

操作系统

2009-11-03 09:02:59

Windows 7苹果竞争
点赞
收藏

51CTO技术栈公众号