Python闭包

发布于 2021 年 08 月 02 日 | 更新于 2021 年 08 月 17 日

闭包形成条件

  1. 在函数中定义函数。分别称为:
    • 外部函数:Enclosing function
    • 内部函数Enclosed function,Inner function,Nested function
  2. 外部函数的返回值是内部函数
  3. 内部函数使用了外部函数的变量,称为自由变量

自由变量

内部函数(外部函数的返回值)会被延迟执行,执行内部函数需要用到外部函数定义的变量,那么就只有将这些(自由)变量保存起来;又Python万物皆对象,函数也是一个对象,因此就将这些自由变量保存到内部函数的成员变量__closure__

__closure__是一个tuple,包含内部函数需要的所有外部函数的变量(自由变量)

def outer(x):
    a = 5
    b = 2  # 没有被使用,也就没保存到__closure__里面
    def inner():
        print(x, a)
    return inner

closure_f = outer(1)
closure_f()  # 1 5
# 获取自由变量名
print(len(closure_f.__code__.co_freevars))  # 2
print(closure_f.__code__.co_freevars[0])  # 'a'
print(closure_f.__code__.co_freevars[1])  # 'x'
# 获取自由变量值,index与变量名一致
print(len(closure_f.__closure__))  # 2
print(closure_f.__closure__[0].cell_contents)  # 5
print(closure_f.__closure__[1].cell_contents)  # 1

如果外部函数返回多个内部函数,这些内部函数共享(浅拷贝)一个__closure__,也就是说,其中一个函数修改了自由变量,其他函数访问的自由变量也会跟着改变

def outer(x):
    funcs = []
    for a in range(x):
        def inner():
            nonlocal a
            a += 1
            return a
        funcs.append(inner)
    return funcs

closure_f = outer(3)

print([f() for f in closure_f])  # -> [3, 4, 5]

修改自由变量

这个规则跟全局变量很相似,只不过global用于全局变量,nonlocal用于嵌套函数的外部变量(表明该变量即不在全局作用域也不在局部作用域)

  1. 不可变类型(int, str等)不能直接修改,要要加上nonlocal修饰才能修改
  2. 可变类型(list等)可以修改其内容,如append+=
def outer(x):
    a = [5]
    def inner():
        nonlocal x
        print(x, a)
        x += 1
        a[0] += 1
    return inner

closure_f = outer(1)
closure_f()  # 1 5
closure_f()  # 2 6
closure_f()  # 3 7

Python Inner Functions: What Are They Good For? – Real Python