Python 闭包

作者: Rethink | 来源:发表于2019-05-07 15:22 被阅读0次

    Python v3.7.0

    在函数嵌套的程序结构中,如果内层函数包含对外层函数局部变量的引用,同时外层函数的返回结果又是对内层函数的引用,这就构成了一个闭包。当外层函数在调用结束时,发现自己的局部变量在内层函数中有引用,就会把这个局部变量绑定到内部函数,然后自己再结束。

    以一个实现可变参数求和的函数为例,函数体定义如下:

    def calc_sum(*args):
        ax = 0
        for n in args:
            ax += n
        return ax
    

    但是,如果不需要立刻返回求和结构,而是在后面的代码中,根据需要再计算怎么办?此时可以不返回求和的结果,而是返回求和的函数,修改后的代码如下:

    def lazy_sum(*args):
        def sum():
            ax = 0
            for n in args:
                ax += n
            return ax
    
        return sum
    
    s = lazy_sum(1, 2, 3, 4, 5)
    print(s, type(s),s(),sep='\n')
      
     
    # Output>>>
    <function lazy_sum.<locals>.sum at 0x10768ed90>
    <class 'function'>
    15
    

    在上面的代码中,我们在外层函数lazy_sum中又嵌套了的内层函数sumsum引用了lazy_sum的参数,并且lazy_sumsum 作为返回结果。如果是按照命令式语言的规则(如C++,C#),在执行sum函数时,会由于在其作用域内找不到args变量而出错,但是在函数式语言中,当内嵌的函数体内有引用外部作用域的变量时,将会把定义时涉及到的引用环境和函数体打包成一个整体返回,这中程序结构就是上面说的"闭包(Closure)"。所以说,闭包就是由函数及其相关的引用环境组合而成的实体,即:闭包=函数+引用环境。

    从运行结果中可以看到,当调用lazy_sum(*args)时,返回的是内部求和函数的引用地址,当调用函数sum() 时,才真正计算求和的结果。

    进一步理解Python中的闭包概念,还有两点需要特别注意,首先看下面的例子:

    s1 = lazy_sum(1, 2, 3, 4, 5)
    s2 = lazy_sum(1, 2, 3, 4, 5)
    print(s1==s2)
    
    # Output>>>
    False
    

    也就是说当我们调用lazy_sum()时,即使传入参数相同,每次调用也都会返回一个新的函数,彼此不会互相影响。

    第二点,在闭包中修改外部作用域的局部变量时,需要使用关键字nonlocal,否则会报错。稍微修改一下上面求和的代码,如下:

    # ax = 0  # UnboundLocalError
    def lazy_sum(*args):
        ax = 0  # UnboundLocalError
        def sum():
            # ax = 0  # 正常
            for n in args:
                ax += n
            return ax
    
        return sum
      
    s = lazy_sum(1, 2, 3, 4, 5)
    print(s, type(s),s(),sep='\n')
    
    # Output>>>
    UnboundLocalError: local variable 'ax' referenced before assignment
    

    我们尝试将原本定义在内层函数中的变量 ax 放到了外层函数或函数体外部中进行定义,此时运行代码都会抛出UnboundLocalError 的异常,这再看下面的代码:

    def decorator():
        name = "Rethink"
        def wrapper():
            print(name)
        return wrapper
    
    deco()()
    
    # Output>>>
    Rethink
    

    这里看到,代码可以正常运行,这是因为在闭包中只是引用了外部作用域的局部变量,而没有修改它的值。

    在闭包结构中,内层函数改变外层函数的局部变量需要用nonlocal 关键字, nonlocal不能定义新的外层函数变量,只能改变已有的外层函数变量,同时也不能改变全局变量,在看下面的例子:

    # ax = 0  # no binding for nonlocal 'ax' found
    def lazy_sum(*args):
        ax = 0
        def sum():
            nonlocal ax
            for n in args:
                ax += n
            return ax
        return sum
    
    s = lazy_sum(1, 2, 3, 4, 5)
    print(s, type(s), s(), sep='\n')
    
    # Output>>>
    <function lazy_sum.<locals>.sum at 0x000001B0D80612F0>
    <class 'function'>
    15
    

    从运行结果中可以看到,在闭包函数中使用 nonlocal 声明外层函数中定义的ax变量后,程序可以正常运行,但是如果ax是定义在函数体外部的全局变量,则运行函数时,会报错:no binding for nonlocal 'ax' found .

    [To be continued...]

    参考文档

    1. Python基础|深入闭包与变量作用域,公众号:编程时光

    2. 深入理解Python变量作用域与函数闭包石晓文

    3. Python与算法社区,Emily

    相关文章

      网友评论

        本文标题:Python 闭包

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