什么是 Python 的闭包?
定义
闭包(Closure)是指一个函数记住了其定义时所在作用域的变量,即使该作用域已经执行完毕、不再存在,这些变量依然可以被内层函数访问和使用。
构成闭包的三个条件:
- 存在嵌套函数(函数内定义函数)
- 内层函数引用了外层函数的变量(自由变量)
- 外层函数将内层函数作为返回值返回
基础示例
def outer(x):
def inner(y):
return x + y # inner 引用了外层变量 x
return inner # 返回内层函数
add5 = outer(5) # outer 执行完毕,但 x=5 被"记住"
print(add5(3)) # 8
print(add5(10)) # 15
add5 就是一个闭包,它携带着 x=5 这个自由变量。
查看闭包捕获的变量
print(add5.__closure__) # (<cell at 0x...>,)
print(add5.__closure__[0].cell_contents) # 5
常见陷阱:循环中的闭包
# ❌ 错误:所有函数共享同一个 i
funcs = [lambda: i for i in range(3)]
print([f() for f in funcs]) # [2, 2, 2]
# ✅ 正确:用默认参数将 i 的值固定在每次迭代
funcs = [lambda i=i: i for i in range(3)]
print([f() for f in funcs]) # [0, 1, 2]
原因:闭包捕获的是变量本身(引用),而非变量的值。循环结束后 i=2,所有 lambda 都读到同一个 i。
nonlocal 关键字
在闭包内部修改外层变量时,需要使用 nonlocal 声明:
def counter():
count = 0
def increment():
nonlocal count # 声明修改外层变量
count += 1
return count
return increment
c = counter()
print(c()) # 1
print(c()) # 2
print(c()) # 3
不加 nonlocal 直接赋值会报 UnboundLocalError,因为 Python 会将其视为局部变量。
典型应用场景
1. 函数工厂
def make_multiplier(n):
def multiplier(x):
return x * n
return multiplier
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
2. 装饰器(本质就是闭包)
def logger(func):
def wrapper(*args, **kwargs):
print(f"调用 {func.__name__}")
return func(*args, **kwargs)
return wrapper
@logger
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
# 调用 greet
# Hello, Alice!
3. 延迟计算 / 缓存状态
def make_adder(base):
history = []
def add(x):
result = base + x
history.append(result)
return result, history
return add
adder = make_adder(10)
print(adder(1)) # (11, [11])
print(adder(2)) # (12, [11, 12])
闭包 vs 类
| 闭包 | 类 | |
|---|---|---|
| 状态存储 | 自由变量 | 实例属性 |
| 代码量 | 少,轻量 | 多,结构清晰 |
| 适用场景 | 简单状态、单一行为 | 复杂状态、多个方法 |
总结
- 闭包 = 内层函数 + 捕获的外层自由变量
- 核心价值:让函数携带状态,是装饰器、函数工厂等模式的基础
- 注意循环中的变量捕获陷阱,修改外层变量需用
nonlocal