在python中,如果一个函数里面嵌套了另一个函数,这种情况就是函数嵌套。
如下面的例子:
def out_func():
def inner_func1():
print("The first inner function.")
return
def inner_func2():
print("The second inner function.")
return
inner_func1()
inner_func2()
return
out_func()
注意:
这里要注意的是我们无法在外部函数外直接调用内部函数。
看到这里我们可能一头雾水,我也是,为什么要在一个函数里面定义另一个函数呢?不能直接定义某个函数直接实现某个功能,然后另外一个函数来调用它吗?
上面的函数完全可以这样实现呀,如下:
def inner_func1():
print("the first inner func")
return
def inner_func2():
print("the second inner func")
return
def out_func():
inner_func1()
inner_func2()
return
out_func() # 调用out_func函数
这不是一样的吗?
不着急,我们先留着这些问题,慢慢继续看下去。
在一个外函数内定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用,这样就构成了一个闭包。
一般情况下,如果一个函数结束,函数内部所有的东西都会释放掉,还给内存,局部变量都会消失。但是闭包是一种特殊情况,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。
# 闭包函数的实例:outer()是外部函数,a和b都是外部函数的临时变量
def outer(a):
b = 10
# inner()是内部函数
def inner():
# 在内部函数中用到了外部函数的临时变量
print(a+b)
# 外部函数返回值是内部函数的引用
return inner
# 调用外部函数,传入参数5。此时外函数两个临时变量a是5 b是10,并创建了内部函数,然后把内部函数的引用返回给了demo进行存储,外部函数结束的时候发现内部函数将会调用到自己的临时变量,这两个临时变量就不会释放,会绑定给这个内部函数
demo1 = outer(5)
# demo存储了外部函数的返回值,也就是inner()函数的引用,这里相当于执行inner函数
demo1() # 15
demo2 = outer(7)
demo2() # 17
# 修改闭包变量实例
def outer(a): # outer是外部函数,a和b都是外部函数的临时变量
b = 10 # a和b都是闭包变量
c = [a] # 这里对应修改闭包变量的方法2
def inner(): # inner是内部函数
# 内函数中想修改闭包变量
nonlocal b # 方法1 nonlocal关键字声明,此处的b是outer()函数中的b
b += 1 # b=11
c[0] += 1 # 方法2 把闭包变量修改成可变数据类型,比如:列表
print(c[0], b)
return inner # 外部函数返回内部函数的引用
demo = outer(5)
demo() # 6 11
这里要注意一下nonlocal这个关键字,上面已经说过了,Python3中,可以使用nonlocal关键字声明一个变量,表示这个变量不是局部变量空间的变量,需要向上一层变量空间找这个变量;再解释一下,我们是在内部函数中声明变量b,如果我们不加nonlocal关键字,那么我们取到的b就不是外部函数中定义的b.这里涉及到一个作用域的问题,我们在内部函数中正常定义的函数,我们只能在内部函数中使用。但是nonlocal可以在上一层变量空间寻找指定变量,并对其进项操作。
在闭包中,内层函数可以访问外层函数的变量,但是不可写,我们来看看下面的例子:
# 修改闭包变量实例
def outer(a): # outer是外部函数,a和b都是外部函数的临时变量
b = 10 # a和b都是闭包变量
c = [a] # 这里对应修改闭包变量的方法2
def inner(): # inner是内部函数
# # 内函数中想修改闭包变量
# nonlocal b # 方法1 nonlocal关键字声明
# b += 1
#
c[0] += 1 # 方法2 把闭包变量修改成可变数据类型,比如:列表
print(c[0], b)
return inner # 外部函数返回内部函数的引用
demo = outer(5)
demo() # 6 10
在这里内部函数,直接打印了外部函数的变量b和c[0],并且对列表里面的第一个元素进行了操作,这里又有一个问题了,不是说只能访问不能写吗?为什么又能对列表中的元素进行操作了呢?这里就要说到一个问题了,我们这里再来理解一下所谓的不能写是什么意思,其本质是改变元素变量在内存空间的地址指向,就是说我的变量b不能指向另一个内存地址。我们直接这样来试试,就可以了,这个例子好像不能说明问题,为什么上面的例子可以对c[0]进行操作呢,因为c指向的列表的内存地址没有变化,只是这个列表的第一个元素的内存地址指向发生了变化,我们关心的是外部函数的变量的内存地址的变化。
# 修改闭包变量实例
def outer(a): # outer是外部函数,a和b都是外部函数的临时变量
b = 10 # a和b都是闭包变量
c = [a] # 这里对应修改闭包变量的方法2
def inner(): # inner是内部函数
# # 内函数中想修改闭包变量
# nonlocal b # 方法1 nonlocal关键字声明
# b += 1
#
# c[0] += 1 # 方法2 把闭包变量修改成可变数据类型,比如:列表
c = [a,a]
print(c[0], b)
print(c)
return inner # 外部函数返回内部函数的引用
demo = outer(5)
demo() # 6 10
注意:使用闭包的过程中,一旦外部函数被调用一次返回了内部函数的引用,虽然每次调用内部函数是开启一个函数执行过后消亡,但是闭包变量实际上只有一份,每次开启内部函数都在使用同一份闭包变量,如下代码:
def outer(x):
def inner(y):
nonlocal x
x += y
return x
return inner
demo = outer(10)
print(demo(1), demo(3)) # 11 14
说了半天,就说了什么是闭包?但是还是没有说清楚为什么要用闭包,下面就说说为什么。
函数执行后返回结果是一个内部函数,并被外部变量所引用,如果内部函数持有被执行函数作用域的变量。即形成了闭包。
可以在内部函数访问到外部函数作用域。使用闭包,一可以读取函数中的变量,二可以将函数中的变量存储在内存中,保护变量吧被污染。而正是因为闭包会把函数中的变量存储在内存中,会对内存有消耗,所以不能滥用闭包,否则会影响网页性能,造成内存泄漏。当不需要使用闭包时,要及时释放内存,可将内存函数对象的变量赋值为null。
函数执行分为两个阶段(预编译和执行阶段)。
1、在预编译阶段,如果发现内部函数使用了外部函数的变量,则会在内存中创建一个“闭包”对象并保存对应变量值,如果已存在“闭包”,则只需要增加对应属性值即可。
2、执行完后,函数执行上下文会被销毁,函数对“闭包”对象的引用也会被销毁,但其内部函数还持用该“闭包”的引用,所以内部函数可以继续使用“外部函数”中的变量。
利用了函数作用域链的特性,一个函数内部定义的函数会将包含外部函数的活动对象添加到它的作用域链中,函数执行完毕,其执行作用域链销毁,但因内部函数的作用域链仍然在引用这个活动对象,所以其活动对象不会被销毁,直到内部函数被烧毁后才被销毁。
1、可以从内部函数访问外部函数的作用域中的变量,且访问到的变量长期驻扎在内存中,可供之后使用。
2、避免变量污染全局。
3、把变量存到独立的作用域,作为私有成员存在
1、对内存消耗有负面影响,因内部函数保存了对外部变量的引用,导致无法被垃圾回收,增大内存使用量,所以使用不当会导致内存泄漏。
2、对处理速度具有负面影响。闭包的层级决定了引用的外部变量在查找时经过的作用域长度。
3、可能获取到意外的值。
说到底还是一句话,我们外部函数的变量在执行外部函数后,外部变量不希望被回收,因为后面可能还有用,所以要使用闭包保证外部变量不会被回收。