【超详细解释】一文带你了解python中的函数嵌套和闭包

python 函数嵌套与闭包


文章目录

  • python 函数嵌套与闭包
  • 一、python中的函数嵌套
  • 二、闭包
  • 三、为什么要用闭包呢?
    • 1、什么是闭包
    • 2、闭包的原理
    • 3、闭包的优点
    • 4、闭包的缺点


一、python中的函数嵌套

在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
  1. 外函数返回内函数的引用:在Python中一切都是对象,当我们进行变量赋值时(a=1),实际上在内存中已经存储了值1,然后用a这个变量名存储1所在内存位置的引用。引用就好比C语言的指针,可以理解为地址。a只是变量名,a里面存储的是1的这个值所在的地址,就是a里面存储了数值1的引用。同理,当我们在Python中顶一个函数def demo():时,内存中会开辟一些空间,存下这个函数的代码、内部的局部变量等等。这个demo只不过是一个变量名,它里面存了这个函数所在位置的引用而已。我们还可以进行x=demo,y=demo,这样的操作就相当于把demo里存的内容赋值给x和y,这样x和y都指向了demo函数所在的引用,在这之后我们可以用x()或者y()来调用我们创建的demo(),调用的本质就是执行一个函数,x、y和demo三个变量名存了同一个函数的引用。对于上面的例子,在外部函数outer中最后返回inner,我们在调用外部函数demo=outer()时,outer返回了inner,inner是内部函数的引用,这个引用被存入了demo中,所以接下来我们再进行demo()时,相当于执行了inner函数。
  2. 在内部函数中想修改闭包变量(外部函数绑定给内部函数的局部变量)时:Python3中,可以使用nonlocal关键字声明一个变量,表示这个变量不是局部变量空间的变量,需要向上一层变量空间找这个变量;Python2中没有nonlocal这个关键字,可以把闭包变量改成可变类型数据进行修改,比如:列表。
# 修改闭包变量实例
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

三、为什么要用闭包呢?

说了半天,就说了什么是闭包?但是还是没有说清楚为什么要用闭包,下面就说说为什么。

1、什么是闭包

函数执行后返回结果是一个内部函数,并被外部变量所引用,如果内部函数持有被执行函数作用域的变量。即形成了闭包。

可以在内部函数访问到外部函数作用域。使用闭包,一可以读取函数中的变量,二可以将函数中的变量存储在内存中,保护变量吧被污染。而正是因为闭包会把函数中的变量存储在内存中,会对内存有消耗,所以不能滥用闭包,否则会影响网页性能,造成内存泄漏。当不需要使用闭包时,要及时释放内存,可将内存函数对象的变量赋值为null。

2、闭包的原理

函数执行分为两个阶段(预编译和执行阶段)。

1、在预编译阶段,如果发现内部函数使用了外部函数的变量,则会在内存中创建一个“闭包”对象并保存对应变量值,如果已存在“闭包”,则只需要增加对应属性值即可。

2、执行完后,函数执行上下文会被销毁,函数对“闭包”对象的引用也会被销毁,但其内部函数还持用该“闭包”的引用,所以内部函数可以继续使用“外部函数”中的变量。

利用了函数作用域链的特性,一个函数内部定义的函数会将包含外部函数的活动对象添加到它的作用域链中,函数执行完毕,其执行作用域链销毁,但因内部函数的作用域链仍然在引用这个活动对象,所以其活动对象不会被销毁,直到内部函数被烧毁后才被销毁。

3、闭包的优点

1、可以从内部函数访问外部函数的作用域中的变量,且访问到的变量长期驻扎在内存中,可供之后使用。

2、避免变量污染全局。

3、把变量存到独立的作用域,作为私有成员存在

4、闭包的缺点

1、对内存消耗有负面影响,因内部函数保存了对外部变量的引用,导致无法被垃圾回收,增大内存使用量,所以使用不当会导致内存泄漏。

2、对处理速度具有负面影响。闭包的层级决定了引用的外部变量在查找时经过的作用域长度。

3、可能获取到意外的值。

说到底还是一句话,我们外部函数的变量在执行外部函数后,外部变量不希望被回收,因为后面可能还有用,所以要使用闭包保证外部变量不会被回收。

你可能感兴趣的:(python,python,开发语言)