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 没有被回收,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))add5 与 add10 是两份独立闭包,分别记住自己的 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。