闭包的内存开销主要取决于其捕获的自由变量是否为大对象,而非闭包本身大小;安全做法是只捕获轻量值,避免因闭包导致大数据无法被垃圾回收。

直接测闭包本身内存不现实——闭包不是独立对象,而是函数对象附带的 __closure__ 元组和其中的 cell 对象。真正要对比的是:不同闭包结构在实际使用中引发的内存驻留差异。关键不在“闭包多大”,而在“它让哪些数据无法被释放”。
看闭包是否持有大对象引用
闭包的内存开销主要来自它捕获的自由变量(free variables)。如果这些变量是大型数据(如大列表、DataFrame、缓存字典),它们会因闭包存在而延长生命周期。- ✅ 安全写法:只捕获轻量值(小整数、短字符串、函数名)
def make_adder(x): return lambda y: x + y # x 是 int,开销极小 add5 = make_adder(5) - ❌ 高风险写法:闭包内引用了本该短期存在的大数据
def make_processor(big_data): # big_data 可能几 MB,现在被闭包长期持有了 return lambda idx: big_data[idx] * 2 processor = make_processor(large_list) # large_list 不会被 gc,直到 processor 被销毁
用 sys.getsizeof + asizeof 辅助验证
`sys.getsizeof` 对函数对象返回固定基础开销(约 100–150 字节),看不出差异;真正要看的是 **闭包所依赖对象的实际总占用**:-
先查闭包持有的 cell 内容:
import sys from pympler import asizeof def make_closure(data): return lambda: data cl = make_closure([i for i in range(100000)]) # 查看闭包内部引用了什么 if cl.__closure__: for cell in cl.__closure__: print("cell内容类型:", type(cell.cell_contents)) print("cell内容大小:", asizeof.asizeof(cell.cell_contents)) -
对比两种闭包的总内存影响(推荐用
asizeof.asizeof()):- 场景 A:闭包捕获一个 1MB 列表
- 场景 B:闭包捕获一个 10 字节字符串
→ 两者函数对象大小几乎一样,但asizeof.asizeof(cl)差异可达百倍,因为asizeof会递归统计cell_contents
警惕隐式长生命周期:闭包 + 全局存储
最常见泄漏模式:把闭包存进全局容器(如 list、dict、类属性),导致其捕获的所有数据永远不释放。# 危险:全局列表不断累积闭包,每个都拖着自己的 big_data
handlers = []
for cfg in configs:
handlers.append(lambda: process(cfg)) # cfg 被闭包捕获,且 handlers 永远不清理
# 正确:用默认参数切断引用
handlers.append(lambda cfg=cfg: process(cfg)) # cfg 值被绑定,不依赖外部作用域
与等效类实例对比更直观
闭包常被用来替代小型状态类。这时可直接比内存:# 闭包方式
def make_counter():
count = 0
def inc(): nonlocal count; count += 1; return count
return inc
# 类方式
class Counter:
__slots__ = ['count']
def __init__(self): self.count = 0
def inc(self): self.count += 1; return self.count
c1 = make_counter()
c2 = Counter()
-
asizeof.asizeof(c1)≈ 函数对象 + cell(通常 -
asizeof.asizeof(c2)≈ 实例对象 +__slots__存储(约 64–96 B)
→ 两者差距不大,但类更可控;若闭包捕获了额外数据,优势立刻反转。
不复杂但容易忽略:闭包的内存成本不在语法本身,而在它悄悄延长了谁的寿命。











