闭包(closure)
闭包
1)闭包定义
闭包:对于一个嵌套定义的函数(函数中定义函数),外部函数的返回值是内部函数,而在内部函数中又引用了外部函数的局部变量;在外部函数执行结束后,这些局部变量不会消失,会与被返回的内部函数一同存在,此时,内部函数被称为闭包(closure)。
以上理解正确,但还不够一般性, 在计算机科学中 ,闭包(Closure)是词法闭包(Lexical Closure)的简称,是指引用了自由变量的函数, 这些被引用的自由变量将和这个函数共存,即使创造这些自变量的环境已经消亡了,他们也不会消亡,除非函数消亡,他们才消亡。所以,有另一种说法:闭包是由函数和与其相关的引用环境组合而成的实体。
当内部函数引用其外部范围中的值时,可以定义 Python 中的闭包。
闭包提供了某种形式的数据隐藏。闭包可以在一系列函数调用之间保持状态。
进一步说明闭包前,必须先明白python中“函数”的特性,这是实现闭包的前提:
(1)函数是一个对象;
(2)函数中可以定义其他函数;
(3)函数能作为参数传递;
(4)函数能作为返回值;
还必须明白“函数”和“调用函数”的区别,下例中 func 称为函数, func(3) 称为函数调用,后者是对前者传入参数并求值的结果。函数也是一个对象,所以 func 指代一个函数对象,它的值是函数本身; func(3) 是对函数的调用,它的值是调用的结果,次例中其值为5。
def func(a):
b = a + 2
return b
print(func)
print(func(3))
#输出
<function func at 0x00000231340648B0>
5
创建闭包函数的必要条件★★★★★:
(1)必须有一个嵌套的函数;
(2)内部函数必须引用外部函数的变量或参数;
(3)外部函数返回值之一,必须是使用了外部变量或参数的内部函数。
只有同时满足上述三个条件时,内部函数才能叫闭包,否则内部函数只能叫内部函数。
例:
def outer_f(a): #(外部函数)
b = 3
def inner_f(c): #内部函数(闭包函数)
return a + b + c
return inner_f
my_f = outer_f(2)
print(my_f)
print(my_f(4))
print(my_f(5))
print(my_f(6))
#输出
<function outer_f.<locals>.inner_f at 0x0000023132A38C10>
9
10
11
上例中定义了一个嵌套函数(形成闭包的条件1),其中外部函数为outer_f,内部函数为inner_f;
变量a和参数b都属于外部函数,只有变量c属于内部函数,但内部函数用到了外部函数的a和b(形成闭包的条件2);
外部函数最后返回内部函数inner_f(形成闭包的条件3)(注意不是inner_(),前者表示函数本身,后者表示函数执行;
至此,内部函数inner_f就是一个完整的闭包函数了。
2)闭包核心作用
上例中,my_f = outer_f(2),表示将2赋值给a,再将outer_f(2)的返回值inner_f(这是一个函数)赋给my_f,即my_f实际上就是内部函数inner_f,从输出也可以看出这点。注意,到这里inner_f函数还没有被执行。
这一步执行完以后,外部函数outer_f已经被销毁了,意味着a和b也销毁了,但是我们发现后面执行my_f(4)、my_f(5)、my_f(6),结果是9、10、11,显然a和b的值被闭包保留下来了,这正是闭包的核心作用:外部变量保存!
3)闭包修改外部函数的变量
前面定义中明确指出,形成闭包的其中一个条件是内部函数必须使用了外部的变量或参数。但是否意味着可以在内部函数(闭包内)中修改外部变量或参数内?请看下例:
def outer_f(a):
b = 3
def inner_f(c):
a += 4
return a + b + c
return inner_f
my_f = outer_f(2)
print(my_f(4)) #报错
QQ截图20210906192926.jpg
从结果可以看出,a += 4 这步出错,这里闭包inner_f企图对外部变量a进行修改,这是不行的,正常情况情况下,只允许闭包使用外部变量或参数,不允许修改外部变量或参数;
如果要在闭包内部修改外部变量,则可以先在闭包内部使用nonlocal 变量名的方式做声明:
def outer_f(a):
b = 3
def inner_f(c):
nonlocal a,b
a += 4
b += 4
return a + b + c
return inner_f
my_f = outer_f(2)
print(my_f(4))
#输出
17
事实上,内部函数(闭包)会判断其中的变量和参数属于外部还是内部:
(1)如果在闭包中没有重新定义与外部同名的变量,则可以在闭包中使用外部变量(注意不是修改),本文最前面的例子即是如此;
(2)如果在闭包内部没有重新定义外部变量,也没有声明是nolocal,而企图修改外部变量,则报错;
(3)如果在闭包内部重新定义与外部同名的变量,则外部变量会被屏蔽,即虽然同名,内外实际上是两个完全不同的变量;如下例:
def outer_f(a):
b = 3
def inner_f(c):
a = 100
return a + b + c
return inner_f
my_f = outer_f(2)
print(my_f(4))
#输出:
107
上例中a = 100是在inner_f中新定义的变量,其作用范围只有inner_f内部,与外部的a完全没有关系。
注意: 容器式外部变量可在内部函数(闭包)中被修改
前面已经说明,在没有使用nolocal的情况下,不能在闭包中修改外部变量,但如果外部变量是容器式变量,如list等,则可以在闭包中增加或减少元素:
def outer_f():
a = []
def inner_f(b):
a.append(b)
return a
return inner_f
my_f = outer_f()
print(my_f(4))
print(my_f(5))
print(my_f(6))
#输出
[4]
[4, 5]
[4, 5, 6]
上例中列表a是外部函数的变量,企图在没有声明nolocal的情况下,在闭包内部对a扩展元素,结果成功了。值的说明的是,上例中执行了三次my_f()函数,从输出结果可知,后面的结果是基于前面的结果的,这说明三次执行,共用了同一个a,其每执行一次my_f()后,a就变化,可见,闭包函数的一个对象(如这里的my_f)对应一套外部变量,如果是有多个对象,则每个对象之间是独立的,且会单独为每个对象分配一套外部变量,如下面的my_f1和my_f2他们是闭包的两个独立对象,即有两个独立的外部变量a分别与其对应:
(开头程序同上一个程序)
my_f1 = outer_f()
print(my_f1(4))
print(my_f1(5))
print(my_f1(6))
my_f2 = outer_f()
print(my_f2(100))
print(my_f2(101))
print(my_f2(102))
#输出:
[4]
[4, 5]
[4, 5, 6]
[100]
[100, 101]
[100, 101, 102]
4)闭包陷阱
def outer_f():
fs = []
for i in range(3):
def inner_f():
return i * i
fs.append(inner_f)
return fs
fs1, fs2, fs3 = outer_f()
print (fs1())
print (fs2())
print (fs3())
#输出:
4
4
4
上面代码是典型的错误使用闭包的例子,本意是想输出0、1、4,实际却是4、4、4。可见,闭包并没有把外部循环变量i=0、1、2分别记录下来,而是只用了最后的i=2。
这个例子中,外部函数返回的并不是一个闭包函数,而是包含三个闭包函数的一个list。而且三个闭包函数均引用外部函数中定义的同一个自由变量i。
但问题是为什么for循环中的变量变化会影响到所有的闭包函数?而且前面已经说过,同一闭包的不同对象是相互独立的。
其实问题的关键就在于在返回闭包列表fs之前for循环的变量的值已经发生改变了,而且这个改变会影响到所有引用它的内部定义的函数。因为在函数outer_f返回前其内部定义的函数并不是闭包函数,只是一个内部定义的函数。
上述代码跟下面代码一个意思,但更高理解:
def outer_f():
fs = []
j = 0
for i in range(3):
def inner_f():
return j * j
fs.append(inner_f)
j = 2
return fs
fs1, fs2, fs3 = outer_f()
print (fs1())
print (fs2())
print (fs3())
#输出:
4
4
4
5)闭包的作用
1、共享变量时避免使用不安全的全局变量。
2、允许将函数与某些数据关联起来。
3、延伸的作用域都彼此独立。
4、需要动态实现,同时又想保持接口的一致性。
5、较低的内存开销。
6、实现装饰器。
6)参考
https://www.cnblogs.com/yssjun/p/9887239.html
https://zhuanlan.zhihu.com/p/22229197
http://c.biancheng.net/view/5335.html
https://www.jb51.net/article/161402.htm
https://www.jb51.net/article/158236.htm
https://www.jb51.net/article/98604.htm
https://www.jb51.net/article/86383.htm
https://www.jb51.net/article/161754.htm
https://www.jb51.net/article/174564.htm
https://www.jb51.net/article/185802.htm
https://www.jb51.net/article/211926.htm
https://www.jb51.net/article/195088.htm
https://www.jb51.net/article/54498.htm