当前位置: 首页 > 科技观察

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

时间:2023-03-14 16:31:32 科技观察

本文转载自微信公众号《Python中文社区》,作者龚庆奎。转载本文请联系Python中文社区公众号。什么是global和nonlocal在Python支持的关键字中,global和nonlocal初学者接触的很少,不知道它们有什么用;有些人虽然知道它们的作用,但是对于为什么要引入这两个关键字却有些不解。所以是的。粗略地说,global和nonlocal是为了修饰函数中的全局变量和闭包变量而引入的关键字。本文通过代码分析引入global和nonlocal的原因。下面一个奇怪的现象,让我们做一个测试。g=1deffun():g=2returngprint(fun(),g)一般来说,我们认为结果应该是2,2。学过Java、c等其他语言的同学尤其认同这一点。但是让我们运行它,看看结果是2,1。即函数不改变全局变量g。这很奇怪,因为:Python认为所有=赋值都在当前范围内创建变量。当我们在程序中设置g=1时,表示当前全局作用域建立g并赋值1。当我们在函数中设置g=2时,表示当前局部作用域建立g并赋值2、使用dis.dis(fun)分析fun函数源码:160LOAD_CONST1(2)2STORE_FAST0(g)174LOAD_FAST0(g)6RETURN_VALUE可以看出第二条指令是STORE_FAST,是存储到本地的命令多变的。因此,函数中实际操作的是局部变量。下面还有更多例子,大家猜猜执行结果。g=1deffun():g+=1returngprint(fun(),g)根据上面我们知道函数不会改变全局变量g,所以这次结果应该是2,1吧?抱歉,当执行Wheng+=1时,系统报错:UnboundLocalError:localvariable'g'referencedbeforeassignment。仔细观察报错,localvariable'g',这里的g仍然被认为是一个局部变量:如果没有定义(=赋值),直接inplaceadd就可以了,当然会报错。换句话说,对局部范围内的全局变量的所有赋值和就地赋值都将失败。只有以下功能给我们带来了些许安慰。g=1deffun():returngprint(fun(),g)结果为1,1,终于还有一个正常的:在局部作用域引用全局作用域变量是正常的。必须要修改全局变量怎么办?global的引入与分析这是引入global的原因,将全局变量扩展到函数中,让函数可以修改全局变量。g=1deffun():globalgg=2returngprint(fun(),g)结果为2,2,函数修改了全局变量。我们来看dis.dis(fun)的反汇编。370LOAD_CONST1(2)2STORE_GLOBAL0(g)384LOAD_GLOBAL0(g)6RETURN_VALUE第二条指令STORE_GLOBAL是将常量2赋值给全局变量g,与上例中STORE_FAST指令对局部变量的操作不同。因此,我们得出结论,在函数中读取全局变量时,可以直接使用。但是如果需要修改一个全局变量的值,需要在变量前加global来修改。nonlocal的引入同样,当我们写嵌套函数,需要修改闭包中的变量时,我们也需要引入nonlocal关键字。在下面的函数中,我们定义了一个闭包,闭包中的变量e试图在嵌入函数中修改它,但是e没有用nonlocal关键字声明。defouter():e=1definner():e=2returnerreturninner参考上面的例子,我们知道这个修改是徒劳的——因为我们可以看到反汇编代码dis.dis(outer()):630LOAD_CONST1(2)2STORE_FAST0(e)644LOAD_FAST0(e)6RETURN_VALUE第二条指令STORE_FAST操作的是局部变量,也就是说内部的e仍然被认为是局部变量。与上例中的global类似,这里使用nonlocal来修饰内部函数inner中的闭包变量e。defouter():e=1definner():nonlocalee=2returnerreturninner此时查看反汇编代码dis.dis(outer()):780LOAD_CONST1(2)2STORE_DEREF0(e)794LOAD_DEREF0(e)6RETURN_VALUE第二条指令STORE_DEREF,操作是什么闭包变量,也就是说inner中的e就是闭包中可以修改的e。总结本文通过分析函数对全局变量和闭包变量的读写操作,借助反汇编字节码分析,认识了global和nonlocal关键字的用法,并对它们的介绍和作用有了更深入的了解.作者:龚庆奎、大奎,对计算机与电子信息工程感兴趣。