作者整理笔记,记录备查

文章目录

  • 1、变量作用域
  • 2、闭包的概念
  • 3、闭包的作用
    • 3.1 读取函数内部的变量
    • 3.2 让函数内部的局部变量始终保持在内存中
  • 4、注意事项
    • 4.1 内存消耗
    • 4.2 使用场景
    • 4.3 闭包无法改变外部函数局部变量指向的内存地址
    • 4.4 返回函数引用循环变量
  • 5、闭包的__closure__属性

1、变量作用域

还是以局部函数中的例子做参考,假设要在函数外部得到函数内的局部变量a,由于python作用于的搜索顺序,无法通过调用outfuc直接实现,但是通过在内部定义infunc函数,并通过返回该函数对象可以实现对局部变量a的访问。
通过局部函数,可以在函数外部得到函数内的局部变量
这时outfunc 内部的所有局部变量,对infunc都是可见的。但是infunc内部的局部变量,对outfunc 是不可见的

#全局函数
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 ** exponentreturn 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}</{0}>".format(tag_name, content)return add_tagcontent = 'Hello'add_tag = tag('a')
print add_tag(content)
# <a>Hello</a>add_tag = tag('b')
print add_tag(content)
# <b>Hello</b>

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

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

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

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

一般来说,函数内部的局部变量在这个函数运行完以后,就会被Python的垃圾回收机制从内存中清除掉。如果希望这个局部变量能够长久的保存在内存中,可以用闭包来实现这个功能。
以棋盘游戏的为例,假设棋盘大小为50*50,左上角为坐标系原点(0,0),建立一个函数,接收2个参数,分别为方向(direction),步长(step),该函数控制棋子的运动。 注:每次运动的起点都是上次运动结束的终点

def create(pos=[0,0]):def go(direction, step):new_x = pos[0]+direction[0]*stepnew_y = pos[1]+direction[1]*steppos[0] = new_xpos[1] = new_yreturn posreturn goplayer = 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 2176116975280 # outvar
inner x before call inner: invar at 2176116989104 # invar
outer x before call inner: outvar at 2176116975280  # outvar

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

def outfunc ():x = 'outvar'#局部函数def infunc():nonlocal xx = '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 2176116975280 # outvar
inner x before call inner: invar at 2176116989104 # outvar
outer x before call inner: invar at 2176116989104 # outvar地址改变

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

4.4 返回函数引用循环变量

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

def count():fs = []for i in range(1, 4):def f():return i*ifs.append(f)return fsf1, 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*jreturn gfs = []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 ** exponentreturn exponent_of
square = nth_power(2)
#查看 __closure__ 的值
print(square.__closure__) # (<cell at 0x000001FAAB272C70: int object at 0x00007FFAB3332740>,)

参考:
[1] Wayne:用最简单的语言解释Python的闭包是什么?
[2] 千山飞雪:深入浅出python闭包
[3] Python中作用域的搜索顺序

python闭包的作用相关推荐

  1. python 闭包的作用_python中对闭包的理解

    运行环境声明:本人的代码在sublime text 3中写的,可以Ctrl+b运行.python版本是python3.6.如果您直接运行的,请自觉加入if __name__ == '__main__' ...

  2. python有什么作用-Python中的闭包到底有什么用

    1.global关键字的作用 如果在函数中需要修改全局变量,则需要使用该关键字,具体参见下面例子. variable=100 deffunction():print(variable) #在函数内不对 ...

  3. Python闭包基本介绍与作用

    目录 1.闭包基本介绍 1.1 外函数的返回值是内函数的引用 1.2 外函数把临时变量绑定给内函数 1.3 闭包中内函数修改外函数局部变量 2.闭包的作用 1.闭包基本介绍 "闭包" ...

  4. python闭包到底有什么作用

    本文转载于https://blog.csdn.net/qq_27825451/article/details/79964128,并对代码格式做了修正. 1.global关键字的作用 如果在函数中需要修 ...

  5. python中闭包的作用_Python闭包及其作用域

    Python闭包及其作用域 关于Python作用域的知识在python作用域有相应的笔记,这个笔记是关于Python闭包及其作用域的详细的笔记 如果在一个内部函数里,对一个外部作用域(但不是全局作用域 ...

  6. Python 闭包详解

    在函数编程中经常用到闭包.闭包是什么,它是怎么产生的及用来解决什么问题呢.给出字面的定义先:闭包是由函数及其相关的引用环境组合而成的实体(即:闭包=函数+引用环境)(想想Erlang的外层函数传入一个 ...

  7. python 闭包_一篇文章读懂Python的闭包与装饰器!

    什么是装饰器? 装饰器(Decorator)相对简单,咱们先介绍它:"装饰器的功能是将被装饰的函数当作参数传递给与装饰器对应的函数(名称相同的函数),并返回包装后的被装饰的函数", ...

  8. Python闭包与装饰器

    Python闭包与装饰器 一.闭包       函数是一个对象,所以可以对象的形式作为某个函数的结果返回.函数执行完后内部变量将会被回收.在闭包中,由于内部函数存在对外部函数的变量的引用,所以即使外部 ...

  9. python 闭包和装饰器详解_实力讲解,一文读懂Python闭包与装饰器!

    什么是装饰器? 装饰器(Decorator)相对简单,咱们先介绍它:"装饰器的功能是将被装饰的函数当作参数传递给与装饰器对应的函数(名称相同的函数),并返回包装后的被装饰的函数", ...

最新文章

  1. Java引入依赖aar_java – 如何将JAR依赖项包含到AAR库中
  2. ubuntu14安装mysql5.6_ubuntu14.04安装mysql5.6.37
  3. 非阻塞connect的实现
  4. linux 修改超级权限密码,linux 修改用户密码
  5. 16年蓝桥杯省赛-方格填数(dfs+回溯)
  6. mybatis学习(51):扩展集
  7. 00_python安装与配置(mac)
  8. 长得类似铁甲小宝的机器人_铁甲小宝中十大机器人战斗力排行榜,第一谁都猜到了...
  9. 度度熊的01世界 DFS
  10. 中国水密门市场趋势报告、技术动态创新及市场预测
  11. redis安装部署(1)
  12. 网上支付之易宝支付接入规范
  13. 2124. OIBH杯第三次模拟赛(普及组)Problem 1 : tictac 立体井字棋
  14. 单节锂电池充电管理芯片,IC电路图
  15. 七、入门python第七课
  16. 【海思篇】【Hi3516DV300】六、音频输入篇
  17. CSS布局示例 1 - 页面色块布局
  18. 车间制造管理系统(下)
  19. C语言位运算的高级应用(尤其适合单片机和嵌入式编程)
  20. 企业wifi统一认证怎样呀

热门文章

  1. 微信小程序授权小知识
  2. centos7安装docker并配置php运行环境
  3. python发红包问题_python 模拟发红包程序
  4. odoo14教程(制造订单与销售订单关联)
  5. 1092: 【入门】时间的差! 题解
  6. 打开IE浏览器后自动跳转到Edge浏览器怎么办
  7. Appium配置华为鸿蒙系统
  8. 微信分享朋友圈遇到的坑(分享点取消也是走的分享成功)
  9. 关于new Date()获取当前时间
  10. iphonex适配游戏_苹果的充值已到账 《王者荣耀》终于适配iPhoneX