写点什么

python3 的变量作用域规则和 nonlocal 关键字,Python 面试及答案

作者:程序媛可鸥
  • 2022 年 3 月 17 日
  • 本文字数:2900 字

    阅读完需:约 10 分钟

对于 python 来说也是一样的问题,python 代码在执行前首先会被编译成字节码,这就会导致某些时候实际执行的程序会和我们看到的产生出入。不过我们有 dis 模块帮忙,它可以输出 python 对象的字节码,下面我们就来看下经过编译后的 f: > dis(f) 2 0 LOAD_GLOBAL 0 (print) 2 LOAD_FAST 0 (i) 4 CALL_FUNCTION 1 6 POP_TOP 3 8 LOAD_CONST 1 ('a') 10 STORE_FAST 0 (i) 4 12 LOAD_GLOBAL 0 (print) 14 LOAD_FAST 0 (i) 16 CALL_FUNCTION 1 18 POP_TOP 20 LOAD_CONST 0 (None) 22 RETURN_VALUE 其中`LOAD_FAST 和 STORE_FAST`是读取和存储 local 作用域的变量,我们可以看到,i 变成了局部作用域的变量!而对 i 的赋值早于 i 的定义,所以报错了。 产生这种现象的原因也很简单,python 对函数的代码是独立编译的,如果未加说明而在函数内对一个变量赋值,那么就认为你定义了一个局部变量,从而把外部的同名对象屏蔽了。这么做无可厚非,毕竟 python 没有独立的声明一个局部变量的语法,但结果就会造成我们看到的类似暂时性死区的现象。所以请允许我把 es6 的概念套用在 python 身上。 **消除暂时性死区** 既然知道问题的症结在于 python 无法区分局部变量的声明和定义,那么我们就来解决它。 对于一个可以区分声明和定义的语言来说是没有这种烦恼的,比如 c: ''' 遇到问题没人解答?小编创建了一个 Python 学习交流 QQ 群:778463939 寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和 PDF 电子书! ''' int i = 0; void f(void) { i++; printf("%d\n", i); // 1 const char *i = "hello"; printf("%s\n", i); // "hello" } python 中不能这么做,但是我们可以换一个思路,声明一个变量是全局作用域的,这样不就解决了吗? global 运算符就是为了这个目的而存在的,它声明一个变量始终是全局作用域的变量,因此只要存在 global 声明,那么当前作用域里的这个名字就是一个对同名全局变量的引用。改进后的函数如下: def f(): global i print(i) i += 1 print(i) 现在运行程序就会是你想要的结果了: > python test.py 0 1 1 如果你还是不放心,那么我们再来看看字节码: > dis(f) 3 0 LOAD_GLOBAL 0 (print) 2 LOAD_GLOBAL 1 (i) 4 CALL_FUNCTION 1 6 POP_TOP 4 8 LOAD_CONST 1 ('a') 10 STORE_GLOBAL 1 (i) 5 12 LOAD_GLOBAL 0 (print) 14 LOAD_GLOBAL 1 (i) 16 CALL_FUNCTION 1 18 POP_TOP 20 LOAD_CONST 0 (None) 22 RETURN_VALUE 对于 i 的存取已经由`LOAD_GLOBAL`和`STORE_GLOBAL`接手了,没问题。 当然 global 也有它的局限性: * 一旦声明 global,那么这个名字始终是 global 作用域的一个变量,不可以再是局部变量 * 名字必须存在于 global 里,因为 python 在运行时进行名字查找,所以你的变量在 global 里找不到的话对它的引用将会出错 * 接上一条,因为 global 限定了名字查找的范围,所以像闭包作用域的变量就找不到了 事实上需要引用非 global 名字的需求是极其常见的,因此为了解决 global 的不足,python3 引入了 nonlocal **使用 nonlocal 声明闭包作用域变量** 假设我们有一个需求,一个函数需要知道自己被调用了多少次,最简单的实现就是使用闭包: ''' 遇到问题没人解答?小编创建了一个 Python 学习交流 QQ 群:778463939 寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和 PDF 电子书! ''' def closure(): count = 0 def func(): # other code count += 1 print(f'I have be called {count} times') return func 还是老问题,这样写对吗? 答案是不对,你又制造暂时性死区啦! >>> f=closure() >>> f() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 5, in func UnboundLocalError: local variable 'count' referenced before assignment 这时候就要 nonlocal 出场了,它声明一个名字位于闭包作用域中,如果闭包作用域中未找到就报错。 所以修正后的函数如下: def closure(): count = 0 def func(): # other code nonlocal count count += 1 print(f'I have be called {count} times') return func 测试一下: >>> f=closure() ![](https://static001.geekbang.org/infoq/a7/a74ca620f34c4bb6888d6f206d64adae.png) 如果你也是看准了 Python,想自学 Python,在这里为大家准备了丰厚的免费**学习**大礼包,带大家一起学习,给大家剖析 Python 兼职、就业行情前景的这些事儿。 ### 一、Python 所有方向的学习路线 Python 所有方向路线就是把 Python 常用的技术点做整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。 ![](https://static001.geekbang.org/infoq/e0/e03fd26f6c0917195450c6199bf5a2fd.png) ### 二、学习软件 工欲善其必先利其器。学习 Python 常用的开发软件都在这里了,给大家节省了很多时间。 ![](https://static001.geekbang.org/infoq/74/74032b26d9da5ee03e17a76e12edbb15.png) ### 三、全套 PDF 电子书 书籍的好处就在于权威和体系健全,刚开始学习的时候你可以只看视频或者听某个人讲课,但等你学完之后,你觉得你掌握了,这时候建议还是得去看一下书籍,看权威技术书籍也是每个程序员必经之路。 ![](https://static001.geekbang.org/infoq/22/222a67ec7a63430cd1a38da4c40c4d64.png) ### 四、入门学习视频 我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了。 ![](https://static001.geekbang.org/infoq/c8/c839377ce45db4a46eaa5f64f5b28871.png) ![](https://static001.geekbang.org/infoq/79/796c29008052d2c5d1a0a30ef3b50e63.png) ### 四、实战案例 光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。 ![](https://static001.geekbang.org/infoq/40/40fc2e4f5073880c93a277921a062e0a.png) ### 五、面试资料 我们学习 Python 必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。 ![](https://static001.geekbang.org/infoq/30/3082765585de5293d8850a9ce8389cea.png) 成为一个 Python 程序员专家或许需要花费数年时间,但是打下坚实的基础只要几周就可以,如果你按照我提供的学习路线以及资料有意识地去实践,你就有很大可能成功! 最后祝你好运!!!

用户头像

Python编程资料加Q群免费领取:419829237 2022.03.14 加入

还未添加个人简介

评论

发布
暂无评论
python3的变量作用域规则和nonlocal关键字,Python面试及答案_Python_程序媛可鸥_InfoQ写作平台