Python闭包指:内层函数在外层函数返回之后,仍然记着并使用外层作用域里的变量。计数器、带配置的小工厂、装饰器里的 wrapper,都建立在这一机制上。要修改被捕获的外层变量时,需要 nonlocal

会记住状态的函数

def make_counter():
    count = 0
    def inner():
        nonlocal count
        count += 1
        return count
    return inner

c = make_counter()
print(c())
print(c())
print(c())

make_counter 返回后,局部变量 count 并未随之消失,而是继续被 inner 引用。每次调用 c(),都在同一份 count 上累加。

构成条件

  • 存在嵌套函数。
  • 内层函数引用了外层函数的变量。
  • 外层函数把内层函数作为值返回(或传到别处长期保存)。
make_counter() 的调用环境 count = 0 外层变量(调用完不消失) inner() 内层函数 count += 1 return count 捕获并记住
make_counter() 调用结束后,count 没有被回收,inner 把它捕获记下来了,c() 每次调用都在上次基础上 +1。
def outer(x):
    def inner(y):
        return x + y
    return inner

add5 = outer(5)
add10 = outer(10)

print(add5(3))
print(add10(3))

add5add10 是两份独立闭包,分别记住自己的 x

和全局变量对比

把因子封在闭包里,不必往模块顶层挂可变全局量,多个乘法器也可以并存:

def make_mul(factor):
    def mul(x):
        return x * factor
    return mul

double = make_mul(2)
triple = make_mul(3)
print(double(5))
print(triple(5))

延迟绑定配置

先传入问候语,得到专门的问候函数,稍后只再传人名:

def make_greeter(greeting):
    def greet(name):
        return f"{greeting}, {name}!"
    return greet

say_hi = make_greeter("你好")
say_morning = make_greeter("早上好")

print(say_hi("小明"))
print(say_morning("小红"))

和装饰器的关系

装饰器返回的 wrapper 捕获了原函数 func,这正是闭包。上一篇的计时、日志装饰器,都可以从这个角度理解:

def log(func):
    def wrapper(*args, **kwargs):
        print("调用前")
        result = func(*args, **kwargs)
        return result
    return wrapper

@log
def hello():
    print("hi")

hello()

常见误区

  • 内层要改外层变量却未声明 nonlocal 时,赋值只会新建内层局部名,外层值不变。
  • 在循环里批量创建闭包时,若直接引用循环变量,捕获的是变量本身。循环结束后它们往往都变成最终值。需要冻结当时的值,可用默认参数,例如 lambda n=i: n