Python 闭包到底捕获了啥?一个计数器例子讲透,别再背定义了

结论先行:闭包(closure)不是"函数套函数"的花活,而是------一个内层函数"记住了"它定义时所在外层作用域里的变量,哪怕外层函数早就执行完了返回了。记住的不是值,是那个变量本身(绑定)。一句话:闭包 = 内层函数 + 它捕获的外部变量,打包带走。


1. 先搞清:什么叫"捕获了外部变量"

普通函数里的局部变量,函数一返回就被销毁。但如果一个函数里定义了另一个函数,并且这个内层函数用到了外层的变量,Python 会把那些变量"封存"进内层函数的 __closure__ 里带走。

python 复制代码
def make_counter():
    count = 0                  # 外层局部变量
    def inc():
        nonlocal count
        count += 1
        return count
    return inc                 # 返回内层函数,count 被一起带走

c = make_counter()
print(c())                     # 1
print(c())                     # 2
print(c())                     # 3

关键点 :count 没有随 make_counter 返回而消失------它被 inc 捕获了,活在了 c 这个闭包里。你每调一次 c(),操作的都是同一个被捕获的 count。

2. 怎么验证"带走的是变量,不是值"

看闭包对象自带的证据:

python 复制代码
print(c.__closure__)           # (<cell at ...: int object>,)
print(c.__closure__[0].cell_contents)   # 3  ← 当前捕获值

为什么重要:捕获的是变量绑定,所以后续修改对闭包"可见"。这正是计数器能累加的原因。若你以为是"传值",就解释不了为什么三次调用共享状态。

3. 最经典的坑:循环里闭包捕获同一个变量

python 复制代码
funcs = []
for i in range(3):
    def f():
        return i               # 捕获的是 i 这个变量,不是 0/1/2
    funcs.append(f)

print([f() for f in funcs])    # [2, 2, 2]  ← 全都是循环结束后的 i=2

为什么翻车 :三个 f 捕获的是同一个 i,循环跑完 i=2,于是全返回 2。这不是 bug,是闭包语义本身。修法两种:

python 复制代码
# 法一:用默认参数把当时的值"钉死"
for i in range(3):
    def f(i=i):
        return i
    funcs.append(f)

# 法二:用 functools.partial / lambda 绑定
from functools import partial

4. 闭包 vs 类 vs 全局变量(到底该用谁)

维度 闭包 类 全局变量
状态保存在 __closure__ 单元格 实例 __dict__ 模块命名空间
多个独立状态 每个闭包一份 每个实例一份 全程序共享一份
可读性 轻量但隐晦 显式清晰 易污染
适用 小型工厂/回调/装饰器 状态复杂、要方法 几乎别用

对照结论:要的是"带状态的小函数"→ 闭包;状态多、要方法 → 用类;全局变量能不用就不用(并发下炸得最隐蔽)。

5. 一个反直觉点:闭包捕获的是"变量",延迟绑定要小心

闭包里的变量是延迟求值的------只有真正调用时才去读当前值。上面第 3 节的循环坑,本质就是延迟绑定 + 共享变量。记住口诀:循环里造闭包,要么默认参数钉值,要么 partial 绑定,别裸捕获循环变量。


对照表:一眼分清

场景 正确写法 错误写法
计数器/累加器 nonlocal + 闭包 或 类 裸捕获外层变量
循环生成多个函数 默认参数 i=i 或 partial 裸 return i
只读配置 闭包捕获常量 全局变量

排错清单(收藏级)

  • SyntaxError: no binding for nonlocal :nonlocal 声明的变量外层必须真存在,且不能是全球量;漏写 nonlocal count 直接报。
  • 循环闭包全返回同一个值 :延迟绑定 + 共享变量(见第 3 节),用默认参数或 partial 钉值。
  • 闭包修改捕获值报错 :忘了 nonlocal/global,Python 会当成新建局部变量 → 加 nonlocal。
  • 装饰器里的闭包不生效 :装饰器本质是闭包工厂,确认内层真的引用了外层参数;@wraps 别忘了保留元信息。
  • 内存泄漏担忧:闭包持有外部变量,只要闭包对象活着,那些变量就不释放;长生命周期闭包别捕获大对象。

一句话记住 :闭包就是"内层函数把外层变量打包带走"------带走的是变量绑定不是值,循环里造闭包记得钉值,要改捕获值就 nonlocal。

相关推荐
心疼你的一切3 小时前
Python自动发邮件实战定时发报表
大数据·开发语言·python
卡提西亚4 小时前
FastAPI基础入门
python·fastapi
fanruitian5 小时前
python 机器学习 ML LinearRegression KNN KMeans
python·机器学习·kmeans
鱼宵5 小时前
取集合第一个元素,Java 为什么要写三种写法?
java·windows·python·java21
卷无止境5 小时前
给AI装上一双眼睛:快速构建代码验证器的思路与落地方案
后端·python
维克兜率天5 小时前
【维克】指数调整:哪些股票会被“强制买入“?
人工智能·python·算法
come112345 小时前
Codex 下不同场景 模式选择
python
神也畏惧菠萝6 小时前
Day08 Python流程控制从入门到上瘾:if/match/while/for四件套,从B站登录一路打到猜数字游戏
python·算法·数据存储·for·while·if·ifelse
言乐66 小时前
通过 3D 动画直观演示球形瓶内可滑动半圆机构在倾倒时防粘盖、回正时刮膏体的运动学过程
python·3d·django·virtualenv·pygame
quantdash_cc6 小时前
ETF 行情数据清洗实战:字段语义拆解、标的池整理与五个校验清单
python·数据分析·pandas·量化交易·股票数据·quantdash