1、什么是闭包

在介绍闭包概念前,我们先来看一段简短的代码

def sum_calc(*args):
def wrapper():
sum = 0
for n in args:
sum += n;
return sum
return wrapper

很显然,这段代码定义了一个名为sum_calc的函数,但和定义的普通函数不同的是这个函数体的内部又定义了一个名为wrapper的函数,并且sum_calc函数的返回值是内部定义wrapper函数。

现在我们开始来调用sum_calc函数,看看会出现哪些有趣的事情

>>> f = sum_calc(1, 2, 3, 4, 5)
>>> f
<function sum_calc.<locals>.wrapper at 0x0000025693AC2D30>
>>> f()
15

从运行结果来看,当我们调用sum_calc时,返回的并不是求和结果,而是内部定义的求和函数。

继续调用sum_calc返回函数时,才真正计算出求和的结果。

当我们继续调用一次sum_calc,传入相同参数

>>> f1 = sum_calc(1, 2, 3, 4, 5)
>>> f2 = sum_calc(1, 2, 3, 4, 5)
>>> f1 == f2
False
>>> f1()
15
>>> f2()
15

每次调用sum_calc,即使传入相同的参数,两次返回的对象不同,且f1()和f2()的结果互不影响。

在这个例子中,我们在sum_calc函数中定义的wrapper函数,wrapper函数可以引用外部函数sum_calc的参数和局部变量,当sum_calc返回函数wrapper时,相关参数保存在返回的函数中,可以被返回的函数继续使用,我们把这种情况称为“闭包”。

参考维基百科,对“闭包”进行更严谨的解释:

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。

2、闭包陷阱

def my_fun():
fs = []
for i in range(0, 3):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count()

在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都放在列表中返回。

我们可能认为,f1()、f2()、f3()结果是0、1、4,但实际结果是4

>>> f1()
4
>>> f2()
4
>>> f3()
4

这个例子可以说是典型的错误使用闭包的案例,my_fun返回的并不是一个闭包函数,而是一个包含三个闭包函数的一个列表。

在返回闭包列表fs之前for循环的变量的值已经发生改变了,循环内闭包函数仅声明了自己计算方式,并不会立即使用当前i的值进行计算。只有在真正调用闭包函数时,才会真正的执行闭包函数内的计算,而此时存放的i的值已经是2,所以f1()、f2()、f3()的结果是4而不是我在之前期待的0、1、4。

经过上面的分析,我们得出下面一个重要的经验:返回闭包中不要引用任何循环变量,或者后续会发生变化的变量。

正确写法

def my_fun(*args):
L = []
for i in range(3):
def wrapper(_i = i):
return _i * _i
L.append(wrapper)
return L

3、闭包实现机制

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

还以 sum_calc() 为例,当其被调用时,可以通过 __closure__ 属性获取自由变量(也就是程序中的 args参数)内容,例如:

def sum_calc(*args):
def wrapper():
sum = 0
for n in args:
sum += n;
return sum
return wrapper

输出结果:

>>> f1 = sum_calc(1, 2, 3, 4, 5)
>>> f1.__closure__
(<cell at 0x0000025693A5E4C0: tuple object at 0x0000025693AE5400>,)

>>> f1.__closure__[0].cell_contents
(1, 2, 3, 4, 5)

可以看到,显示的内容是一个tuple类型,这就是f1中自由变量args的值。还可以看到,__closure__ 属性的类型是一个元组,这表明闭包可以支持多个自由变量的形式。

最新文章

  1. android 混淆导致友盟反馈出错
  2. 移动web开发和移动app开发的区分
  3. C#图片上传服务器缩放存储功能
  4. 委托Delegate,多播委托和委托链
  5. Android UI 绘制过程浅析(二)onMeasure过程
  6. Android Support v4,v7,v13
  7. hdu2030java
  8. [置顶] poj1416数字切割解题报告
  9. jjs 产生undefined的情况
  10. 对于Web开发来说 8 个最好的跨平台编辑器
  11. oracle创建user具体指示
  12. java连接sqlserver2008
  13. loadrunner中的web_convert_param函数
  14. [编译] 5、在Linux下搭建安卓APP的开发烧写环境(makefile版)—— 在Linux上用命令行+VIM开发安卓APP
  15. Android测试中monkey的使用方法
  16. [SDOI2015]序列统计(多项式快速幂)
  17. 使用CCS调试基于AM335X的SPL、Uboot(原创)
  18. VUE 打包后关于 -webkit-box-orient: vertical; 消失,导致多行溢出不管用问题
  19. 学习笔记-AngularJs(四)
  20. mysql建立索引的一些小规则

热门文章

  1. C++高级程序员进阶之路
  2. Vite ❤ Electron——基于Vite搭建Electron+Vue3的开发环境【一】
  3. moviepy音视频剪辑:视频剪辑基类VideoClip的__init__构造方法参数has_constant_size的作用
  4. 第9.4节 Python中用readline读取二进制文件方式打开文件
  5. PyQt(Python+Qt)学习随笔:Qt Designer中部件的toolTip、toolTipDuration、statusTip、whatsThis属性
  6. 使用cmd制作图片木马
  7. 网鼎杯 fakebook
  8. zookeeper未授权访问渗透测试及修复方法
  9. vue结合element-ui实现二级复选框checkbox
  10. vue 编程式导航