社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

Python 为什么引入这两个关键词

Python中文社区 • 3 年前 • 465 次点击  


啥是 global 和 nonlocal

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

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

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

一个奇怪的现象

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

g =1def 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 =1def fun():    g += 1    return gprint(fun(),g) 

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

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

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

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

g =1def fun():    return gprint(fun(),g) 

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

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

global 的引入和分析

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

g =1def fun():    global g    g = 2    return gprint(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

赞 赏 作 者






点击下方阅读原文加入社区会员

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/116428
 
465 次点击