创建时间: 2026-08-25最后更新: 2026-08-25

1. 认识

闭包(closure)不是一种新的数据类型,而是一种函数关系:一个函数定义在另一个函数内部,并且引用了外层函数的变量。即使外层函数已经执行结束,内部函数仍然可以使用那些变量。

可以把它理解成「函数和它需要的环境一起被保存下来」。下面的 make_greeting() 执行后返回了内部函数 greet(),返回的函数仍然记得当时传入的 language:

greeting.py
01
def make_greeting(language):
02
def greet(name):
03
return f"{language},{name}!"
04
05
return greet
06
07
say_hello = make_greeting("你好")
08
say_morning = make_greeting("早上好")
09
10
print(say_hello("小明")) # 你好,小明!
11
print(say_morning("小红")) # 早上好,小红!

make_greeting() 返回的不是问候语字符串,而是一个函数。每次调用 make_greeting() 都会创建一份独立的外层变量,因此 say_hello 和 say_morning 互不影响。

2. 捕获

内部函数读取外层函数的变量时,就形成了闭包。这个变量也叫「自由变量」:它不是内部函数的参数,也不是内部函数里新定义的局部变量,但内部函数仍然能够访问它。

capture.py
01
def make_multiplier(factor):
02
def multiply(number):
03
return number * factor
04
05
return multiply
06
07
double = make_multiplier(2)
08
triple = make_multiplier(3)
09
10
print(double(5)) # 10
11
print(triple(5)) # 15

调用 make_multiplier(2) 时,factor 的值被 multiply() 捕获。之后即使 make_multiplier() 已经返回,double() 仍然可以使用 factor = 2。

闭包保存的是变量的绑定关系,而不是把代码复制一份。不同的外层调用会产生不同的绑定关系,这也是工厂函数能够创建多个定制函数的原因。

3. 修改

内部函数可以读取外层变量,但如果要给这个变量重新赋值,需要使用 nonlocal。它表示「修改最近一层外层函数中的变量」:

counter.py
01
def make_counter(start=0):
02
count = start
03
04
def next_count():
05
nonlocal count
06
count += 1
07
return count
08
09
return next_count
10
11
counter = make_counter(10)
12
print(counter()) # 11
13
print(counter()) # 12
14
print(counter()) # 13

如果没有 nonlocal,count += 1 会被当作在 next_count() 内创建局部变量。Python 会先读取这个尚未赋值的局部变量,于是抛出 UnboundLocalError。

nonlocal 只能用于嵌套函数中的外层局部变量,不能用来修改模块级变量。修改全局变量需要 global,但在实际项目中通常更推荐把状态放进对象,或通过参数和返回值显式传递。

4. 用途

闭包常用于「函数工厂」:先配置一次规则,再得到一个可以重复使用的函数。例如,为不同角色创建权限检查器:

permission.py
01
def require_permission(required):
02
def check(user_permissions):
03
return required in user_permissions
04
05
return check
06
07
can_edit = require_permission("edit")
08
can_delete = require_permission("delete")
09
10
print(can_edit({"read", "edit"})) # True
11
print(can_delete({"read", "edit"})) # False

装饰器也是闭包的常见应用。装饰器通常返回一个内部函数,由它记住原函数,并在调用前后增加日志、鉴权或计时逻辑:

decorator.py
01
from functools import wraps
02
03
def log_call(function):
04
@wraps(function)
05
def wrapper(*args, **kwargs):
06
print(f"开始调用:{function.__name__}")
07
result = function(*args, **kwargs)
08
print("调用结束")
09
return result
10
11
return wrapper
12
13
@log_call
14
def add(left, right):
15
return left + right
16
17
print(add(2, 3))

这里的 wrapper() 捕获了外层的 function。@log_call 语法会把 add 替换为 log_call(add) 返回的 wrapper,所以调用 add(2, 3) 时会先执行附加逻辑。

在 FastAPI 或 LangChain 项目中,也可以用闭包生成带有固定配置的处理函数,例如为不同模型预先绑定模型名称,再把生成的函数交给路由或工具调用。闭包适合保存少量、相对稳定的配置;配置较多或状态复杂时,使用类通常更清晰。

5. 陷阱

闭包捕获的是变量,而不是创建闭包那一刻的值。循环中创建函数时,所有函数可能会引用同一个循环变量,这称为「晚绑定」:

late-binding.py
1
functions = []
2
3
for number in range(3):
4
functions.append(lambda: number)
5
6
print([function() for function in functions]) # [2, 2, 2]

循环结束后,number 的值是 2,所以三个函数调用时都读取到 2。如果希望每次循环都保存当前值,可以使用默认参数在创建函数时绑定它:

bind-now.py
1
functions = []
2
3
for number in range(3):
4
functions.append(lambda number=number: number)
5
6
print([function() for function in functions]) # [0, 1, 2]

实际项目中,看到「循环里创建回调函数」时要特别注意这个问题。也可以把当前值传给另一个函数工厂,让每次调用都创建一个独立的闭包。

闭包还会延长被捕获对象的生命周期。如果闭包捕获了很大的列表、文件句柄或其他资源,长期保存这个闭包可能造成额外的内存或资源占用,因此需要及时释放不再使用的函数引用。

正在验证登录状态
请稍候,验证完成后将继续显示文章内容