Python之函数高级:一篇文章彻底搞懂什么是“闭包”

发布时间:2026/8/5 0:07:38
Python之函数高级:一篇文章彻底搞懂什么是“闭包” 引言在编程中有一个让一些新手望而却步的概念——“闭包”。一方面, 闭包的定义以及作用机制着实略微有那么点复杂, 另一方面, 这个概念好像更倾向于数学、理论的范畴, 对于新手而言, 好像并没有太多的应用场景。不管怎样, 因由前面已阐述过的, 像是函数对象相关, 函数具备一等公民属性这件事还有高阶函数部分, 以及针对函数对象内部细节展开的深入探究等种种情况。我坚信去领会并熟练掌握“闭包”所涵盖的概念以及运用方式, 会朝着更为简易的方向发展, 这属于那种“顺理成章、自然而然就达成”的状况。本文的主要内容有1、探究“闭包”概念背后的本质2、一个闭包实例3、闭包的作用机制探究“闭包”概念背后的本质我们在前面的文章里, 曾提到编程学习之中的两大核心, 也就是“数据的表达”以及“数据的处理”, 那也能够表示成“数据结构”还有“算法”, 实际上, 这是一种极其简单的表达。从更加完整的角度去看, 每一个程序更为完整的构成, 都必然要有以下几个部分:1、输入数据的集合2、内部状态的集合3、计算规则的集合4、输出数据的集合这实际上同样是计算机科学背后那个图灵机的一种抽象, 从这个抽象层面来讲, 要是去看平常的编程语言里的函数, 我们能够发觉, 好像是图灵机的一个删减版本, 被删减的部分乃是“内部状态的集合”, 也就是说呀, 纯粹的函数是更着重于计算规则, 而不存在内部状态的那般的图灵机。然而, 存在某些时段, 我们的确必要内部状态, 鉴于内部状态各异, 对相同输入而言, 函数会给出不同输出。便是这样, “闭包”的概念出现了, 闭包能够大致被理解成“存在内部状态的函数”, 它是一种组合, 是一个函数跟与其相关的上下文环境状态的组合了。像这样去看的话, 闭包实际上就是一个单纯简省的物体, 该种物体仅仅拥有一项办法。反过来进行观察, 对象能够被视作是几个有着共享内部状况的闭包, 这样的一种说法同样不存在不合理之处, 是能够站得住脚的。故而, 闭包以及对象, 存有这样一种关系, 即看似是两个不同的概念, 实则又紧密相连, 从本质层面来讲, 它们就是同一个事物。依据此情况, 我们能够再进一步推导得出, 函数式编程跟面向对象编程, 在底层的基础层面上同样是属于一致的这一最终结论。要是处于别的函数式编程语言里面, 领会函数, 明白闭包, 理解对象好像还有些繁杂。然而, 要是我们将关注点汇聚到中去看, 因“一切皆对象”这个处于底层的“一”的存有, 在中的函数属于对象, 其自身同样是具备状态也就是属性的。那么, 于中好像我们又无需严格去区分啥是函数、啥是闭包了。因为在中的函数皆是对象, 都存有状态, 有状态的函数便是闭包, 所以在中一切函数似乎也皆是闭包了。的确没错, 上文提及的仅仅是依据底层观念而开展的某些推导罢了。于真实的编程语法层面而言, 平常的函数与闭包存在微微的差异。关联上一篇文章里头, 针对函数对象内里构成的探寻, 我们能够获取如下的论断:1、中的函数全都是对象, 因而, 从宽泛的意义来讲, 中的函数都能够是具备状态的函数。2、中的函数状态就是其定义执行的上下文环境。3、中的普通函数, 它的内部状态, 即其定义所在的全局命名空间, 此状态存在于函数对象内, 能够借助属性予以读写。4、里边的特殊函数对象, 专门指的是“闭包”, 它的内部状态, 除了属性对应的全局命名空间之外, 还有一个属性存储的, 是“闭包”所独有的内部状态。暂且中断对于概念的阐述, 只要存有一定理念性质的感知就行, 之后, 我们借助实际的代码去进一步展现这些概念。一个闭包实例给定一个带初始值的累加器的实现作为例子, 分别去看普通函数的使用情况, 并且去看闭包的使用情况, 进而对闭包的特点展开分析, 同时对闭包在特定场景里的优势所在进行比较。在真正使用闭包之前我们先看一下普通的有状态的函数的使用首先定义一个模块m1.pytotal 20def acc(step):totaltotal step然后我们在入口文件中导入m1模块进行函数调用m1print(f初始值为{m1.total})print(f函数内部状态{m1.acc.})m1.acc(5)print(f加5之后变为{m1.total})print(f函数内部状态{m1.acc.})m1.acc(10)以格式化方式输出, 输出内容为: 又加了10之后变为: 加上变量m1中的total属性的值。print(f函数内部状态{m1.acc.})执行结果来看执行结果, 中所定义的平常函数的确是存有状态的, 并且达成了累加器的成效。但是, 我们也应当认识到, 普通函数的状态, 从本质上来看, 并非仅仅归属于函数自身, 而是与函数所属模块里的所有函数、代码块共同分享的模块自身的全局命名空间。所以, 这个状态明显是欠缺安全性的, 毕竟全局变量里所贮存的状态, 其被更改的渠道颇为多样。除此之外在于, 因状态共享致使的不安全存在着, 我们是没有办法去达成多个不一样状态的累加器能够同时存在的这种情况的。下面我们通过闭包实现这个累加器的需求def acc(total20):def inner(step):totaltotal steptotalinneracc1 acc(0)print(acc1.)print(f初始状态{acc1..})print(f加10之后变为{acc1(10)})print(f加10之后变为{acc1(100)})acc2 acc(100)print(acc2.)print(f初始状态{acc2..})print(f加10之后变为{acc2(10)})print(f加10之后变为{acc2(100)})print(acc1)print(acc2)执行结果分析执行结果可知, 这般“闭包”的形式实实在在达成了各异累加器对象的同时存在, 闭包所特有的内在状态被存储于函数对象的属性里面。闭包的作用机制我们现粗略给出一个“闭包”的定义所谓“闭包”, 是指在嵌套定义的函数里, 有一个内部函数引用了外部函数中的局部变量, 而且该外部函数的作用域已然结束, 这种内部函数和它所应用的外部函数的变量一同构成了闭包。闭包可以保存和访问它创建时的环境即使在外部函数结束之后。前面曾经提及过, 高阶函数存在两个条件, 只要满足其中一个便属于高阶函数也就是说, 要么把函数当作参数来进行传递, 要么返回一个函数对象。闭包明显是满足第二个条件的高阶函数。下面我们来简单概括一下创建一个闭包所需要的条件1、存在嵌套定义的函数。2、内部函数引用了其外部函数中的变量。3、有一个外部函数存在, 它返回了这样一个对象, 此对象是内部函数对象, 要注意, 这里返回的是函数对象, 并非是内部函数完成调用之后所产生的结果, 是有区别的。同时满足上面的3个条件就可以创建闭包了。从本质的角度来讲, 每一个函数都存在着自身所特有的局部具有限定范围的命名空间, 同时也有着归其所属的全局性的命名空间。一旦, 当闭包得以创建的条件达到了满足的状态时, 就必然会生成一个全新的命名空间, 也就是被称作闭包命名空间的那部分空间。只要是在嵌套函数定义里, 内部函数引用了外部函数那一块儿的局部变量, 这样子才会被添加到闭包命名空间范围中, 这是一定要做到的, 对此是严谨规定的, 有着明确要求的。再次看一下定义能瞧见, 函数对象里, 指向闭包命名空间的属性, 是个元组, 其元素类型, 为_Cell, 而那_Cell对象, 是有属性的。所以, 在上面代码里, 对闭包命名空间中所存储的内部状态进行访问, 是借助..得以实现的。此外, 倘若要 在内部函数作用域里, 尝试去修改外部函数当中定义的局部变量, 需知外部函数的形参同样是外部函数的局部变量, 那么就需要借助关键字来对变量展开声明, 不然的话就会被解释器认定为是在定义一个内部函数里的局部变量, 并且还会给其赋上初始值。在前面函数的局部作用域里, 当我们尝试去修改全局变量时, 碰到过类似关键字声明的情况, 也就是关键字。从和这两个被称作关键字的玩意儿当中, 同样是能够瞧得出来闭包作用域那尴尬之处的, 因为并没有使用关键字, 所以并非全局命名空间, 也就是并非local 它可不是局部命名空间。处于全局命名空间以及局部命名空间夹缝中间的那一部分, 暂且权且把它叫作闭包命名空间好了。从名字解析所依照的LEGB规则来看, 能发现, Llocal与G之间的E, 此前未曾展开提及, 实际上就是这个闭包命名空间, 其中的名字解析契合就近原则, 内部的能够覆盖外部的。实际上, 于我而言, 没必要专门对“闭包”的概念予以着重强调, 只要可以明白函数自身身为对象, 已然自然而然地拥有内部状态这样的特性。明白了这些底层的作用运行机制, 就已然充分了。总结本文对闭包的定义予以了详尽阐释, 对其本质也进行了细致说明, 还针对函数、闭包、对象这几个范畴的区别与联系作出了解释, 与此同时, 介绍了闭包得以成立的条件以及与之相关的内部状态的存储机制。在对闭包有了理解的基础之上, 又一次回顾了LEGB规则。感谢您的拨冗阅读希望对您理解中的闭包能有所帮助。