美文网首页
闭包(closure)

闭包(closure)

作者: 马尔代夫Maldives | 来源:发表于2021-09-09 16:32 被阅读0次

    闭包

    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

    相关文章

      网友评论

          本文标题:闭包(closure)

          本文链接:https://www.haomeiwen.com/subject/conewltx.html