Python 为什么引入这两个关键词
啥是 global 和 nonlocal
Python 支持的关键词里,global 和 nonlocal 初学者接触的少,不知道是做什么用的;一些人虽然知道它们的作用,但对为什么要引入这两个关键词则有些不知其所以然。
粗浅地说,global 和 nonlocal 是为了在函数中修改全局和闭包变量而引入的关键字。
本文用代码一点点分析引入 global 和 nonlocal 的原因。
一个奇怪的现象
下面,让我们做一个测试。
g =1
def fun():
g = 2
return g
print(fun(),g)
一般地,我们认为结果应该为 2,2
。这一点学过其他语言如 Java、c 的同学尤其认同。
但让我们跑起来,可以看到结果为 2,1
。也就是说,函数没有改变全局变量 g。
这很奇怪,究其原因是因为:
•Python 认为所有 = 赋值都是在当前作用域新建变量。• 当我们在程序中 g = 1
时,表示当前全局作用域建立 g,赋值 1。• 当我们在函数中 g = 2
时,表示当前局部作用域建立 g,赋值 2。
使用 dis.dis(fun)
分析 fun 函数源代码:
16 0 LOAD_CONST 1 (2)
2 STORE_FAST 0 (g)
17 4 LOAD_FAST 0 (g)
6 RETURN_VALUE
可见,第 2 条指令 STORE_FAST
,这是存储到局部变量的命令。
所以,函数中实际操作的是局部变量。
还有更甚的例子如下,大家猜测下执行结果。
g =1
def fun():
g += 1
return g
print(fun(),g)
根据上文,我们知道函数不会改变全局变量 g,那么结果应该是 2,1
,这次总算对了吧?
很抱歉,当执行到 g += 1
时,系统报错:UnboundLocalError: local variable 'g' referenced before assignment
。
仔细观察错误,local variable 'g'
,这里的 g 仍然被视为局部变量:没有定义(=赋值),就直接 inplace add,当然要报错。
也就是说,所有在局部作用域中对全局变量的赋值、原位赋值都会失败。唯有如下函数给我们带来一丝安慰。
g =1
def fun():
return g
print(fun(),g)
结果 1,1
,总算还有个正常的:在局部作用域中引用全局作用域变量正常。
那当我必须修改全局变量时,该怎么办呢?
global 的引入和分析
这就是 global 引入的理由了,将全局变量扩展到函数中来,使函数可以修改全局变量。
g =1
def fun():
global g
g = 2
return g
print(fun(),g)
结果为 2,2
,函数修改了全局变量。我们来看 dis.dis(fun)
的反汇编代码。
37 0 LOAD_CONST 1 (2)
2 STORE_GLOBAL 0 (g)
38 4 LOAD_GLOBAL 0 (g)
6 RETURN_VALUE
第 2 条指令,STORE_GLOBAL
是将常量 2 赋值给全局变量 g,异于上例中的 STORE_FAST
指令对局部变量操作。
故此,我们得出结论:当在函数中读取全局变量时,可以直接使用。但如果需要修改全局变量值,则需要在变量前加上 global 来修饰。
nonlocal 的引入
同样地,当我们书写嵌套函数,需要对闭包中的变量进行修改操作时,我们也需要引入 nonlocal 关键字。
如下函数中,我们定义了闭包,闭包中的变量 e,试图在内嵌函数中进行修改,但没有使用 nonlocal 关键字声明 e。
def outer():
e = 1
def inner():
e = 2
return e
return inner
参照上例,我们知道这种修改是徒劳的——因为看反汇编代码 dis.dis(outer())
可知:
63 0 LOAD_CONST 1 (2)
2 STORE_FAST 0 (e)
64 4 LOAD_FAST 0 (e)
6 RETURN_VALUE
第 2 条指令 STORE_FAST
,操作局部变量,也就是说 inner 里的 e,仍然被视为局部变量。
雷同于上例的 global,这里使用 nonlocal 来在内嵌函数 inner 中修改闭包变量 e。
def outer():
e = 1
def inner():
nonlocal e
e = 2
return e
return inner
查看此时的反汇编代码 dis.dis(outer())
可知:
78 0 LOAD_CONST 1 (2)
2 STORE_DEREF 0 (e)
79 4 LOAD_DEREF 0 (e)
6 RETURN_VALUE
第 2 条指令 STORE_DEREF
,操作的是闭包变量,也就是说 inner 里的 e,是可以修改的闭包中的 e。
总结
本文通过分析函数对全局变量和闭包变量的读、写操作,借助于反汇编字节码分析,认清了 global 和 nonlocal 关键字的用法,对其引入和作用有了较为深刻认识。
作者:巩庆奎,大奎,对计算机、电子信息工程感兴趣。gongqingkui at 126.com
赞 赏 作 者
点击下方阅读原文加入社区会员