这是一篇通过代码实例,分析闭包概念的笔记。希望能为大家提供一些参考。
问题
首先看两段Python代码的对比,下图左侧代码输出14
,右侧代码输出8
:
刚接触函数式编程时不熟悉闭包概念,所以搞不懂为什么输出值会有14。文章后续内容,是我对程序调试过程的记录。
相关概念
- 函数式编程,意味着函数不仅可以返回数值, 还可以返回需要执行的操作。
Python可以通过def 函数声明
或者lambda 表达式
,把函数作为参数和返回值。 - 一段代码在运行时,可能会调用这段代码之外的函数与变量。这些外部函数与变量构成了代码段的上下文环境。
将构成代码段运行环境的外部内容进行整合,就得到了代码段的一份闭包。 在Python中,可以通过__closure__
属性查看。
代码1 调试记录
如上图左侧所示,测试代码1如下:
# test-1
funcs = []
def foo(m):
for i in range(m):
def bar(n):
return n + i
funcs.append(bar)
foo(10)
print(funcs[3](5)) # 14
在Python中,函数通过值传递方式来接收int
类型参数。
但上述代码的bar()
函数,没有使用正常的参数传递方式,而是直接在函数内引用外部变量i
的值,使得函数foo()
和bar()
产生了内容耦合。
因此,每当外部函数foo()
更改变量i
时,bar()
的运算结果就要受影响。
通过调试可以查看到funcs[]
内部的函数型变量(function variables)bar()
,以及bar()
函数的闭包属性__closure__
:
闭包属性__closure__
内存放了外部变量i
的地址。所以,就如上述分析的一样,随着i
值的递增,结果的数值也在不断变动:
- 当
i==3
时,funcs[3](5)
的结果为 8:- 当
i==9
时,funcs[3](5)
的结果为 14:
如果在bar()
的闭包构建完成后删除变量i
,后续调用bar()
时就会出错:
代码2 调试记录
测试代码2:
# test-2
funcs = []
def foo(m):
for i in range(m):
def bar(n, t=i):
return n + t
funcs.append(bar)
foo(10)
print(funcs[3](5)) # 8
把外部变量i
转为内部函数的默认参数,使得内容耦合降低至数据耦合。调试时,查看funcs[]
的内容可以发现:
- 变量
i
的值被bar()
保留到了__defaults__
属性中,成为了函数bar()
的默认参数; - 由于函数
bar()
内未直接引用i
进行计算,所以__closure__
属性内不再保留外部地址。
此时,随着i
值的递增,结果不再变动。
i==3
时,funcs[3](5)
的结果为 8:i==9
时,,funcs[3](5)
的结果依旧为 8: