python闭包的作用

python闭包的作用详细整理 python 中闭包的定义 作用和注意事项

作者整理笔记,记录备查


1、变量作用域

#全局函数 def outfunc (): a = 'outvar' #局部函数 def infunc(): print(a) #调用局部函数 return infunc #调用全局函数 get_a = outfunc() get_a()  访问变量a的值 

以上infunc函数就形成了闭包,外部函数返回的不是一个具体的值,而是一个函数。一般情况下,返回的函数会赋值给一个变量,这个变量可以在后面被继续执行调用。

2、闭包的概念

维基百科:在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。
维基百科的定义要点如下:

  • 定义:闭包就是能够读取外部函数内的变量的函数。
  • 作用1:闭包将外层函数内的局部变量和外层函数的外部连接起来。
  • 作用2:将外层函数的变量持久地保存在内存中。

计算一个数的 n 次幂,使用闭包实现:

#闭包函数,其中 exponent 称为自由变量 def nth_power(exponent): def exponent_of(base): return base  exponent return exponent_of # 返回值是 exponent_of 函数 square = nth_power(2) # 计算一个数的平方 cube = nth_power(3) # 计算一个数的立方 print(square(2)) # 计算 2 的平方 print(cube(2)) # 计算 2 的立方 

在上面程序中,外部函数 nth_power() 的返回值是函数 exponent_of(),而不是一个具体的数值。

需要注意的是,在执行完 square = nth_power(2) 和 cube = nth_power(3) 后,外部函数 nth_power() 的参数 exponent 会和内部函数 exponent_of 一起赋值给 squre 和 cube,这样在之后调用 square(2) 或者 cube(2) 时,程序就能顺利地输出结果,而不会报错说参数 exponent 没有定义。

3、闭包的作用

3.1 读取函数内部的变量

借用以下案例,实现给content加tag的功能:

def tag(tag_name): def add_tag(content): return "<{0}>{1} 
    ".format(tag_name, content) return add_tag content = 'Hello' add_tag = tag('a') print add_tag(content) # Hello add_tag = tag('b') print add_tag(content) # Hello 

具体使用什么tag_name要根据实际需求来定,对外部调用的接口已经确定,就是add_tag(content),如果使用面向接口的方式,可以这样写:

def add_tag(tag_name,content): return "<{0}>{1} 
    ".format(tag_name, content) add_tag('a','Hello') # Hello 

上面程序确实可以实现相同的功能,不过使用闭包,在添加大量同类型的tag_name时,更加易读和方便,只需要传入content参数

3.2 让函数内部的局部变量始终保持在内存中

def create(pos=[0,0]): def go(direction, step): new_x = pos[0]+direction[0]*step new_y = pos[1]+direction[1]*step pos[0] = new_x pos[1] = new_y return pos return go player = create() print(player([1,0],10)) print(player([0,1],20)) print(player([-1,0],10))  [10, 0] [10, 20] [0, 20] 

这段代码中,player实际上就是闭包go函数的一个实例对象。

它一共运行了三次,第一次是沿X轴前进了10来到[10,0],第二次是沿Y轴前进了20来到 [10, 20],第三次是反方向沿X轴退了10来到[0, 20]。

这证明了,函数create中的局部变量pos一直保存在内存中,并没有在create调用后被自动清除。

为什么会这样呢?原因就在于create是go的父函数,而go被赋给了一个全局变量,这导致go始终在内存中,而go的存在依赖于create,因此create也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

这个时候,闭包使得函数的实例对象的内部变量,变得很像一个类的实例对象的属性,可以一直保存在内存中,并不断的对其进行运算。

4、注意事项

使用闭包,需要注意几点:

4.1 内存消耗

4.2 使用场景

当要实现的功能比较简单的时候,可以用闭包。例如:

  • 当我们的代码中函数比较少的时候,可以使用闭包。但是如果我们要实现很多功能,还是要使用类(OOP)
  • 如果我们的对象中只有一个方法时,使用闭包是会比用类来实现更优雅。

这有点类似于,如果我们要实现比较简单的函数功能,通常使用 lambda 匿名函数比定义一个完整的function更加优雅,而且几乎不会损失可读性。类似的还有用列表解析式代替 for 循环。

4.3 闭包无法改变外部函数局部变量指向的内存地址

def outfunc (): x = 'outvar' #局部函数 def infunc(): x = 'invar' print('outer x before call inner:', x, 'at', id(x)) #调用局部函数 print('outer x before call inner:', x, 'at', id(x)) infunc() print('outer x before call inner:', x, 'at', id(x)) outfunc()   outer x before call inner: outvar at 80 # outvar inner x before call inner: invar at 04 # invar outer x before call inner: outvar at 80 # outvar 

可以看到,局部函数并没有改变外部函数中变量x的地址,这与函数的作用域有关。如果要让内层函数不仅可以访问,还可以修改外层函数的变量,那么需要用到nonlocal声明,使得内层函数不要在自己的命名空间创建新的x,而是操作外层函数命名空间的x

def outfunc (): x = 'outvar' #局部函数 def infunc(): nonlocal x x = 'invar' print('inner x before call inner:', x, 'at', id(x)) #调用局部函数 print('outer x before call inner:', x, 'at', id(x)) infunc() print('outer x before call inner:', x, 'at', id(x)) outfunc()  outer x before call inner: outvar at 80 # outvar inner x before call inner: invar at 04 # outvar outer x before call inner: invar at 04 # outvar地址改变 

infunc改变了 outfunc 中变量x的内存地址

4.4 返回函数引用循环变量

返回闭包时,返回函数不要引用任何循环变量,或者后续会发生变化的变量
以下循环想得到1,4,9,但结果显然没有实现

def count(): fs = [] for i in range(1, 4): def f(): return i*i fs.append(f) return fs f1, f2, f3 = count() print(f1(),f2(),f3())  9 9 9 

返回的函数并没有立刻执行,而是直到调用了f()才执行。因为在向列表中添加 func 的时候,i 的值没有固定到f的实例对象中,而仅是将计算公式固定到了实例对象中。等到了调用f1()、f2()、f3()的时候才去取 i的值,这时候循环已经结束,i 的值是3,所以结果都是9。

如果一定要使用循环,需要再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变

def count(): def f(j): def g(): return j*j return g fs = [] for i in range(1, 4): fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f() return fs f1, f2, f3 = count()  1 4 9 

5、闭包的__closure__属性

闭包比普通的函数多了一个 closure 属性,该属性记录着自由变量的地址。当闭包被调用时,系统就会根据该地址找到对应的自由变量,完成整体的函数调用。

def nth_power(exponent): def exponent_of(base): return base  exponent return exponent_of square = nth_power(2) #查看 __closure__ 的值 print(square.__closure__) # ( 
    
      ,) 
     
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/227081.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月16日 下午9:59
下一篇 2026年3月16日 下午9:59


相关推荐

  • 基于matlab的傅里叶变换「建议收藏」

    基于matlab的傅里叶变换「建议收藏」原文出处例子1作用:使用傅里叶变换找出隐藏在噪声中的信号的频率成分。(指定信号的参数,采样频率为1kHz,信号持续时间为1秒。)由上图可知:从时域中我们很难观察到信号的频率成分。怎么办呢?当然

    2022年7月1日
    30
  • QT-QPainter介绍

    QT-QPainter介绍介绍可以在 QPaintDevice 类上绘制各种图形 QPaintDevice 类表示 QPainter 的绘图设备 画布 QpaintDevice 子类有 QImage QOpenGLPaint QWidget 等 所以 QPainter 可以在 QImage QOpenGLPaint QWidget 上进行绘制图形 QPainter 只能在类对象的 paintEvent 函

    2026年3月18日
    2
  • ES6 模板字符串基本用法[通俗易懂]

    ES6 模板字符串基本用法[通俗易懂]1声明es6中引入新的声明字符串的方式[“]letstr=`es6模板字符串`;console.log(str,typeofstr);2内容中可以直接出现换行符例如letstr=`es6模板字符串`;但是在常用的单引号”双引号中””不可以换行3变量拼接letlove=”沈腾”;letout=love+”xxx是我认为最好的喜剧演员”;letout1=`{love}xxx是我认为最好的喜剧演员`;console.log(out.

    2022年8月21日
    9
  • logo 图标(php图片加文字水印)

    现在很多人都在使用小红书app,但大家肯定都会有同一个问题,图片怎么保存,重点是怎么保存图片无水印。看到好看的图片想保存下来,可是却不喜欢看到有水印。今天,就好好和大家分享一下小红书保存图片途径,往下看不会让你失望。准备工作:电脑一台固乔电商图片助手小红书网站下载步骤:打开电脑下载工具固乔电商图片助手,大家可从浏览器去搜索,也可以直接在乔礼卖家驿站里面去下载这个工具。下载后直接打开就能使用,顺便在…

    2022年4月18日
    33
  • 腾讯AI全景图亮相!混元3D模型开源,智能体开发门槛大降

    腾讯AI全景图亮相!混元3D模型开源,智能体开发门槛大降

    2026年3月12日
    2
  • asuswrt 单臂路由_OPENWRT-KOOLSHARE软路由,一级/单臂/二级/旁软路由设置单臂路由联网教程…

    asuswrt 单臂路由_OPENWRT-KOOLSHARE软路由,一级/单臂/二级/旁软路由设置单臂路由联网教程…【此文原创】此文基于单口软路由讨论一级/单臂路由LAN:IPV4设置除192.168.1.1之外和你局域网不冲突的地址,关闭桥接接口,物理设置eth0WAN:协议改成PPPOE,物理设置eth0二级/旁路由LAN:IPV4地址设置为上级路由网段地址,关闭DHCP。IPV4网关选择上级路由的网关地址。WAN:物理设置-桥接关掉,接口选择eth0-保存并应用客户端机:设置IP地址,网关为软路由地址,I…

    2022年5月16日
    61

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号