所谓闭包,就是指内函数使用了外函数的局部变量,并且外函数把内函数返回出来的过程,这个内函数称之为闭包函数。可以理解为是函数式编程中的封装。
内部函数可以使用外部函数定义的属性:外部函数调用后,返回内部函数的地址,完成内部函数的调用。
def outer():num = 100def inner():return numreturn innerresult = outer()
print(f'外部函数调用后,返回内部函数的地址:{result}')
print('通过内部函数的地址,完成内部函数的调用,使用外部函数定义的属性值为:', result())
一般写法和闭包写法的对比:
一般写法,在 传入的 x 和 y 不变情况下,每次调用函数时,都要重复传入相同的 x、y 参数。
def cal(x,y,z):print(x+y+z)cal(2,3,4)
cal(2,3,5)
cal(2,3,6)
cal(2,3,7)
cal(2,3,8)
闭包写法,在x、y固定的情况下,只需要通过调用内部函数去传 z参数就可以了。如果要计算的结果太多,要传的 z 可能好多,也可以通过循环去实现传参,减少重复调用函数。
def cal(x,y):def cal_in(z):print(x+y+z)return cal_inresult = cal(2,3)
result(4)
result(5)
result(6)
result(7)
result(8)
全局变量对闭包函数调用时所引用的变量究竟有没有影响?
可见,当调用闭包函数时,是直接调用了外部函数变量
n = 3, 而不是全局变量 n = 4, 因此结果为 27。也证明了执行函数时,它的优先级是先搜索内部作用域的变量,全局变量对闭包函数的执行并没有影响。
def pow1():n = 3def pow2(x):return x ** nreturn pow2n = 4result = pow1()
print(result(3))
closure是内部函数的一个属性,用来保存环境变量,返回的是一个元组,每一项都是闭包函数引用的外部变量。可以通过cell_contents 将被引用的变量打印出来。
首先打印 result 的类型,可见是一个函数类型,证明在这个函数里面,我们可以将另外一个函数作为这个函数的返回值去进行返回的。 打印 closure, 得出内部函数的属性所对应的环境变量,可见它是一个元组类型。最后通过 cell_contents去获取变量的值。要注意的是,由于环境变量是一个元组,cell_contents是没有元组直接获取变量值的属性,因此必须先通过索引去获取你要的数据,否则会有报错。
假设把 n = 3 放到最外层,变成全局变量,结果会一样吗?
可见结果已经有刚才的 27 变成了 81。由于 n = 3 已经由局部变量变成了全局变量,当调用 pow2(x)时,并没有引用 pow1() 的变量,因此它并不是闭包函数,只是一个普通的嵌套函数,当pow2(x)搜索内部作用域没找到 n 的变量,就会搜索全局变量,由于全局变量同时存在 n = 3 和 n = 4, 这时变量就相当于 n = 4, 结果就是 3的4次方为81,而不是刚才执行闭包函数时的27了。
同样,根据刚才介绍的 closure 概念,我们可以通过打印 closure 去验证是否闭包。
可见结果为 None, 证明这不是闭包函数。
当 n 未定义的情况下出现的报错:
def pow1():n = 3def pow2(x):n +=1return x ** nreturn pow2result = pow1()
print(result(3))
解决报错的方法:可以用 nonlocal 关键字声明 一个变量, 表示这个变量不是局部变量空间的变量,需要向上一层变量空间找这个变量。
可见添加 nonlocal后,闭包函数可以正常调用 n = 3 并执行累加。因此当调用第一次时,结果为 81, 调用第二次时,结果为 243, n 相当于变成了5
再看另外一个循环的例子,循环中变量分别是1、2,但实际最终返回的结果为4,4。 原因是返回的函数引用了变量i,但它并非立刻执行。等到2个函数都返回时,它们所引用的变量i已经变成了2。从打印 cell_contents 结果可见。
def pow1():n=2L = []for i in range(1,3):def pow2():return i ** nL.append(pow2)return Lf1,f2 = pow1()
print(f1())
print(f2())
print(f1.__closure__)
print(f2.__closure__)
print(f1.__closure__[0].cell_contents)
print(f2.__closure__[0].cell_contents)
要改变分别执行变量1、2, 让pow2() 形成闭包。可见被引用的变量也分别是1,2,返回的结果就是我们预期的1,4
def pow1():n=2L = []for i in range(1,3):def extra(i):def pow2():return i ** nreturn pow2L.append(extra(i))return Lf1,f2 = pow1()
print(f1())
print(f2())
print(f1.__closure__)
print(f2.__closure__)
print(f1.__closure__[0].cell_contents)
print(f2.__closure__[0].cell_contents)
关于更多闭包的高级用法疑问,欢迎留言!