闭包是指延伸了作用域的函数,其中包含函数定义体中的引用和不在定义体中定义的非全局变量。它的特别之处是能够访问定义体之外的非全局变量。这就需要使用内部函数和外部函数了。
1、闭包的条件
(1)在外部函数中定义了内部函数;
(2)外部函数的返回值是内部函数名;
(3)内部函数引用了外部函数的临时变量;
下面是一个闭包的例子,内部函数引用了外部函数的变量,并且外部函数的返回值是内部函数名。然后我们调用外部函数,并用 f 接收外部函数的返回值,可以看出此时 f 指向的地址就是内部函数的地址,也就可以通过 f 调用内部函数。
def outer_fun():
a = 100
def inner_fun():
b = 200
print(a, b)
return inner_fun
f = outer_fun()
print(f) # <function outer_fun.<locals>.inner_fun at 0x0000020A35B25F70>
f() # 100 200
2、闭包的例子
下面的代码,直接运行是会报错的,原因是加 “ * ” 的代码出现错误,原因是对于内部函数来讲,num01是全局变量,而内部函数中定义的变量 num02 是局部变量,它的作用域仅限于内部函数,因此不能在外部函数中调用它。
去掉错误代码行,程序就可以正确运行,输出的结果是100、300。首先调用外部函数是,会执行外部函数函数体,此时只是加载内部函数到内存中,并没有执行内部函数,最终返回内部函数的引用。
def outer_fun():
num01 = 100
def inner_fun():
num02 = 200
print(num01 + num02)
print(num01)
print(num02) # *
return inner_fun
f = outer_fun()
f()
3、闭包的作用
(1)闭包能够保存返回外部函数变量的状态;
(2)闭包能够读取其他函数的内部变量;
(3)闭包延长了函数的作用域;
下面这个代码的输出结果如图所示,调用闭包了三次,输出的结果都是一样的,这是因为在调用了外部函数之后,外部函数执行完毕,此时变量 i 的值为 8 ,外部函数执行完毕,变量 i 对于内部函数来说就相当于全局变量,在每一次调用闭包时都有 i=8,所以三次的输出结果是一样的。
def outer_fun():
for i in range(9):
print(i, end=' ')
print()
def inner_fun(num):
print(i * num,end=' ')
return inner_fun
f = outer_fun()
for i in range(3):
f(3)
4、闭包的缺点
(1)由于延长了函数的作用域,就会使得作用域不够直观;
(2)闭包中的变量不会被垃圾回收,所以存在一直占用内存的问题;
5、闭包的延迟
在闭包中,外部函数返回了内部函数的引用,当外部函数调用结束,内部函数才会保存调用的外部函数的变量,即闭包变量,此时,闭包变量对于内部函数来说相当于全局变量。
闭包延迟是指,只有在调用内部函数时,才会访问闭包变量,不调用时不访问闭包变量。
来看一下非常流行的闭包案例,该案例与匿名函数相结合,需要注意的是,闭包与是不是匿名函数没有关系,关键在于它是否能够访问函数体之外定义的非全局变量。:
def fun():
tmp = [lambda x: i * x for i in range(4)]
return tmp
for t in fun():
print(t(2), end=' ')
上面这个例子的输出结果是 6,6,6,6
造成这样的输出结果的原因在于,内部函数引用了外部函数一个循环的局部变量,此时这个循环的局部变量就是闭包变量。根据闭包延迟的概念,不调用内部函数时,是不访问闭包变量的,外部函数执行结束,循环也就执行结束,当再调用内部函数时,此时内部函数访问的是循环执行结束之后闭包变量的值。在上面这个例子中,循环结束之后 i=3,而外部函数执行结束,返回的是一个包含了四个内部函数引用的列表,然后在列表中,调用内部函数,并传入参数,得到的结果就是 6,6 ,6,6。
这个经典的案例,可以被写成下面这种形式,代码可读性较高,方便理解。
def func():
lst = []
for i in range(4):
def inner_func(num):
return i * num
lst.append(inner_func)
print(lst)
return lst
temp = []
for f in func():
temp.append(f(2))
print(*temp)
输出结果如下图所示,可以清楚的看出,列表 lst 保存了四个内部函数的引用,执行结果与上面的一样:
总结:
(1)可以发现,闭包能够完成之前需要类对象完成的工作,优化了变量;
(2)由于闭包引用了外部函数的局部变量,使得外部函数的局部变量没有及时释放,消耗了内存;
(3)闭包使得代码变得更加的简洁。