
Python 闭包的本质,是函数对象携带了其定义时所在作用域的局部变量(即使外层函数已返回),形成一个“封闭”的执行环境。它不是语法糖,而是函数式编程的重要基础,也是理解装饰器、回调、延迟计算等机制的关键。
闭包怎么形成的?三个必要条件
一个函数要成为闭包,必须同时满足:
- 嵌套结构:内部函数定义在外部函数内部;
- 引用外部变量:内部函数体中直接使用了外部函数的局部变量(非全局变量);
- 返回内部函数:外部函数返回的是内部函数本身(不加括号),而非调用结果。
只有三者齐备,Python 才会将该变量“打包”进内部函数的 __closure__ 属性中,形成真正的闭包。缺一不可。
为什么变量能“活下来”?——闭包的生命周期逻辑
普通局部变量随函数退出而销毁,但闭包中的自由变量(free variable)会被内部函数对象强引用持有,直到内部函数对象被垃圾回收。Python 通过 cell 对象封装这些变量,你可以在 inner.__closure__[0].cell_contents 中读取它的当前值。
立即学习“Python免费学习笔记(深入)”;
注意:闭包捕获的是变量的引用,不是快照。这意味着多个闭包共享同一份自由变量(尤其在循环中易出错)。
经典陷阱题:for 循环 + 闭包,为什么全输出 3?
常见代码:
funcs = []
for i in range(3):
funcs.append(lambda: i)
print([f() for f in funcs]) # 输出 [3, 3, 3]
原因:所有 lambda 都引用同一个变量 i,循环结束时 i == 3,闭包访问的是最终值。
解决方法(任选其一):
- 用默认参数“快照”当前值:
lambda x=i: x; - 用闭包工厂函数封装:
def make_func(x): return lambda: x,再funcs.append(make_func(i)); - 用
functools.partial绑定参数。
面试常考:用闭包实现计数器、配置生成器、简单装饰器
闭包的价值在于“状态+行为”的轻量封装,无需类即可保存私有状态:
-
计数器:
def counter(): n = 0; return lambda: nonlocal n; n += 1; return n; -
配置生成器:如
def make_adder(x): return lambda y: x + y,add5 = make_adder(5); -
装饰器雏形:闭包是装饰器的基础,
@log_calls实际就是外层接收函数、内层执行增强逻辑并返回新函数。
面试时若被问“闭包 vs 类”,可答:闭包适合单状态、轻逻辑场景;类更适合多状态、需继承/多方法的复杂封装。
掌握闭包,关键不在背定义,而在看懂 __closure__ 和 __code__.co_freevars,动手调试几个例子,比死记硬背管用得多。










