Python学习笔记(十一)闭包

本文主要介绍python中的闭包(closure)

闭包的概念

如果在一个函数的内部定义了另外一个函数,外部的函数称外函数,内部的函数称内函数。如果在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用,这就构成了一个闭包。
一般情况,如果一个函数结束,函数内部的所有东西都会被释放掉,还给内存,局部变量都会消失。但是闭包是一种特殊情况,如果外函数在结束时发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。

#闭包实例,outer外函数,inner内函数,a,b是外函数临时变量
>>> def outer(a):
...     b = 10
...     def inner():
...         print(a+b)
...     return inner
...
>>> demo = outer(5)
>>> demo()
15
>>> demo2 = outer(7)
>>> demo2()
17

引用
在python中一切都是对象,包括整型数据1,2,3,函数,其实都是对象。当我们进行a=1的时候,实际上是在内存中有一个地方存了值1,然后a这个变量名存了1所在内存位置的引用。和c语言中的指针类似,可理解为地址。a不过是一个变量名字,它里面存的是1这个数值所在的地址,也就是a里面存了数值1的引用。类似的,在python中定义了一个函数def demo()的时候,内存中会开辟一段空间,存下了这个函数的代码、内部的局部变量等。这个demo只不过是一个变量名字,它里面存了这个函数所在位置的引用而已。进行x=demo,y=demo的操作其实是把demo里存的东西赋值给x,y,这样x,y都指向了demo函数的引用,因此可以用x(),y()来调用我们自己创建的demo(),调用的实际上根本就是一个函数,x,y,demo三个变量名存下了同一个函数的引用。

>>> x = demo
>>> y = demo
>>> x()
15
>>> y()
15

返回函数引用
对于闭包,在外函数outer中,最后return inner,我们在调用外函数demo = outer()时,outer返回了innerinner是一个函数的引用,这个引用被存入了demo中,所以接下来运行demo()的时候相当于运行inner函数。
对于一个函数,如果后面跟一对括号,相当于我们调用该函数,如果不跟括号,相当于只是一个函数的名字,里面存了函数所在位置的引用。
临时变量
内函数用到的外函数的临时变量,在外函数结束时,会绑定给内函数。外函数每调用一次,都会产生一个新的内部函数对象,虽然实现功能一样,但是实际上是不同的函数对象。

>>> demo
.inner at 0x100a7ee18>
>>> x
.inner at 0x100a7ee18>
>>> y
.inner at 0x100a7ee18>
>>> demo2
.inner at 0x100a7eea0>

闭包中内函数修改外函数局部变量

在闭包内可以随意使用外函数临时变量,但是要更改还需注意。
在python语法中,一个函数可以随意读取全局数据,但是要修改全局数据的时候有两种方法:
1.global声明全局变量

>>> a = 10
>>> def change():
...     global a
...     a = a*10
...     return a
...
>>> change()
100
>>> change()
1000

2.全局变量是可变数据类型的时候可以修改(如列表)

l = [1,2,3,4]
>>> def change1():
...     l.append(l[-1]+1)
...     return l
...
>>> change1()
[1, 2, 3, 4, 5]
>>> change1()
[1, 2, 3, 4, 5, 6]

在python3中,可以用nolocal关键字声明一个变量,表示这个变量不是局部变量空间的变量,需要向上一层变量空间找这个变量。在python2中没有该关键字,可以把闭包变量改成可变数据类型数据进行修改,如列表。

>>> def outer(a):
...     b = 10
...     c = [a]
...     def inner():
...         nonlocal b
...         b = b*2
...         c[0] += 2
...         print(b)
...         print(c[0])
...     return inner
...
>>> demo = outer(12)
>>> demo()
20
14

这就是内函数修改闭包变量的方法
python循环中不包含域的概念
例子:

>>> def count():
...     fs = []
...     for i in range(1,3):
...         def f():
...             return i*i
...         fs.append(f)
...     return fs
...
>>> func = count()
>>> func
[.f at 0x10509ae18>, .f at 0x10509aea0>]
>>> func[0]()
4
>>> func[1]()
4

loop在python中是没有域的概念的,fs在向列表中添加f的时候,并没有保存i的值,当执行func = count()时,循环已经结束,此时i=2,可以看出,列表func中的元素是两个函数。
修改1:

>>> def count():
...     fs = []
...     for i in range(1,3):
...         def f():
...             return i*i
...         fs.append(f())
...     return fs
...
>>> func = count()
>>> func
[1, 4]

修改2:再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续怎么更改,已绑定到函数参数的值不变

>>> def count():
...     fs = []
...     for i in range(1,3):
...         def make_f(i):
...             def f():
...                 return i*i
...             return f
...         fs.append(make_f(i))
...     return fs
...
>>> func = count()
>>> func
[.make_f..f at 0x1050a50d0>, .make_f..f at 0x10509af28>]
>>> func[0]()
1
>>> func[1]()
4

闭包的用途

装饰器
面向对象
实现单利模式

你可能感兴趣的:(Python学习笔记(十一)闭包)