| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> Python知识库 -> 简单粗暴的角度看待Python闭包 -> 正文阅读 |
|
[Python知识库]简单粗暴的角度看待Python闭包 |
两段代码案例(运行环境Python3)案例1?使用CallCountPrint装饰器打印Func1,Func2的调用次数
案例2?GetList返回三个函数,各自调用后返回值对比
上面两段代码如果理顺了,估计闭包这个坎也迈过去了。 关于嵌套函数和闭包本人曾在一个写Python的项目组工作,项目规范文档里写着"禁止使用闭包"六个大字,确实,这玩意Hold不住或稍有不慎就容易挖下天坑,如内存泄漏等,但运用得当确实是能写出精妙高级的设计。 嵌套函数和闭包之所以会成为很多人的噩梦,是因为学Python初期各个部分都是十分简单易懂的,但突然来了个断崖式突兀的概念,缓不过来,难以融入自己的理解体系,导致似懂非懂得学一遍忘一遍。其实有一个角度可以帮助我们很丝滑得去理解嵌套函数和闭包。
即面向对象的角度,def定义的代码片段就是一个函数对象,函数对象可被调用执行,也拥有属性,这个函数对象也叫闭包,这个角度而言就是这么简单,事实上也确实这么简单。 面向对象的角度看待两个案例如何看待 def首先不要觉得def这玩意有什么很夸张的语法功能。
完全是跟上述这类赋值语法一视同仁的,变量名=值
运行第1段后,相当于:
运行第2段后,相当于:
这是相同的过程,只是给变量名为Func的变量赋值,赋的什么值?值就是一个函数对象,也即是Func变量引用了一个函数对象,调用Func的时候,Func只是指了一条路,找到那个函数对象并调用。
此时运行Func()是报错的,Func此时是None,运行Func1自然是会打印出hello,因为此时 Func1变量引用了Func原先引用的函数对象。
说白了过程就与上例一样简单,不要想着Func加在def后就有什么特殊的,这仅仅是产生一个函数对象并被一个变量引用的语法上固定要求的写法,嵌套函数只是写法上是函数里定义函数,实际上定义函数的过程其实也就只是一个很简单的创建对象并赋值的过程。 案例1CallCountPrint装饰了Func1函数和Func2函数。
这段代码很一目了然,执行CallCountPrint时变量Wrapper引用了一个新的函数对象并返回,最后 Func1和Func2两个变量分别引用了返回的函数对象。注意,Func1和Func2引用的是两个不同的函数对象,只是内容是一样的,即是定义Wrapper时那一段。 接下来最硬核的一个点: 调用Func1时,实际上是执行Wrapper定义的内容,也就是
讲道理,当Func1被装饰后,它就跟CallCountPrint没任何关系了,CallCountPrint生命周期也结束了,里面定义的参数func,局部变量c等也无了,那Func1在执行时,这些变量c,变量func是从哪里拿的呢? 答案就是从函数对象自身的属性里拿。 要记住一个硬核知识点,在定义这个内嵌函数的时候,这个内嵌函数运行时调用到的那些定义在内嵌函数外的变量,都会被扔到函数对象一个的属性里。至于这个属性叫啥,长啥样,先不用理,可能是个元组,可能是个列表,可能是个字典,反正里面存着函数对象被调用时需要的东西,至于这些变量是以什么形式存在里,存的是变量本身,还是变量引用的对象?也先不用理,反正有关系。 可直接通过打印Func1的属性,也就是CallCountPrint返回的函数对象的属性。
有个属性叫__closure__,翻译就叫闭包,打印一下__closure__。
__closure__ 属性就是一个元组,里面存放着两个cell对象元素,从cell元素描述来看,显而易见,分别与Func1运行时的c和func有关系。 案例1里打印的结果就理顺了,每Func1调用一次,Func都会从自己的__closure__属性里获取到 Func1被装饰时的变量c,和参数func,然后变量c引用的Count类对象的属性mCount+1,并执行参数func引用的函数对象(Func1起初被def赋值的函数对象)。Func2同理。 那案例1里的闭包是什么? 内嵌函数Wrapper和c,func这些内嵌函数外的局部变量一起构成了闭包,其实这个函数对象整体就是闭包。 案例2案例2里容易让初学者难以接受的一点是,这def怎么还又嵌在for里面了?还存到了一个列表里? 与案例1同理,只是把函数对象加到一个列表里而已。
另外一个匪夷所思的现象: 在循环里,生成一个函数对象Print时,由于Print定义时调用了i,obj,因此,i,obj会被保存并且已经保存在了函数对象Print的__closure__里,此刻循环里的情况是: i=0,obj=一个新的object() i=1,obj=另一个新的object() i=2,obj=另另一个新的object() 按平时Python,Java等语言惯性,讲道理在__closure__里的是当下的i,obj引用的对象,即 __closure__里的情况是: __closure__的i=0,__closure__的obj=一个新的object() __closure__的i=1,__closure__的obj=另一个新的object() __closure__的i=2,__closure__的obj=另另一个新的object()
预想中过程应该是与上例效果一致的,一个新的Print函数对象 的__closure__引用了当时obj引用的对象后,obj变量无论引用什么都和上一次循环的Print函数对象的__closure__里保存的obj没有关系了。
但案例2打印结果却与预想大相径庭,说明Print的__closure__里的cell对象并不是直接引用obj引用的对象那么简单,反而像是对变量自身的引用,即Print关注的不是obj变量引用的对象,而是obj变量自身,运行时变量obj引用的是哪个对象,Print调用的就是哪个对象。 ? 学过C++的可能会有感觉,这像是指向指针的指针。 可以简单实验一下
从实验结果来看,这个逻辑是成立的,当删除num变量时,调用PrintNum会报错,因为关注的是变量本身,变量被删除了,PrintNum就相当于尝试访问一个被删掉的东西。 全局变量就更好理解,因为全局变量本身就不需进入闭包的__closure__,运行时直接访问得到。
num这个全局变量在此模块被del后,也就直接报错,全局变量与闭包是没什么关系的。 根据这个规矩,案例2的运行过程和打印结果也是直接理顺了。 需要硬性理解的自由变量上面两个案例里,还有一点不讲道理的情况需要硬性得去认识。
这是固定的规则,在Python层面是理不顺的,知道就行。 像i这些在函数里局部变量,只要不显式得del掉,即使函数调用结束,它就能安全得活下来并被内嵌函数调用。从术语来说,i也称为自由变量。 超多重嵌套函数做个测试,为了区分打印时认清各个变量,各个自由变量分别赋值不同类型的对象:
func2的打印: dictVar和listVar都进入了它的__closure__ ,但Func2并没直接调用到这两个,真正调用到他的是它的子嵌套函数Func3和子子嵌套函数Func4。 func3的打印: dictVar和listVar和setVar都进入了它的__closure__,但Func3并没直接调用到listVar,真正调用到它的是子嵌套函数Func4。 可以大胆得理解,子嵌套函数里的__closure__里的元素来源于上一层函数的局部变量和上一层函数的__closure__里。
上面是执行func3()时的环境,这里和Func1,Func2里定义了啥已没关系了,Func3运行过程里生成Func4函数对象时,Func4会将变量dictVar,setVar,tupleVar扔到它自己的__closure__里,tupleVar是Func3的局部变量,但setVar和dictVar哪里来? 结合Func3的__closure__打印,显而易见,Func4的__closure__里的setVar和dictVar只能来源于Func3的__closure__。 Py2和Py3闭包部分差异
在Inner里尝试重新对num赋值1,结果只是相当于在Inner里生成了一个同名i局部变量,跟外面的 i 没有任何关系,甚至 i 还丧失了进入Inner的__closure__的机会。
Py3引入了nonlocal,使得可以在内嵌函数里修改自由变量的引用。 End相信这些案例如果都理顺了,再去看书本上的解释应该是得心应手了,估计闭包这个坎也算是迈过了,也足够去应付针对Python闭包的开发了。 上述仅仅是基于Python层面上的推敲解释,很多地方描是不准确的,只是方便去辅助理解使用,至于它更准确具体的底层原理,那就得自行深入到c层去研究解释了。 |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/16 1:26:27- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |