分析函数闭包引用的关键是通过__closure__属性查看捕获的自由变量及其值,结合__code__.co_freevars获取变量名,从而还原绑定关系,并注意闭包持留导致的内存问题。

要分析函数闭包引用,关键不是“快照”本身,而是观察闭包实际捕获了哪些变量、它们的值和生命周期。Python 提供了直接可用的机制,不需要第三方工具或内存 dump。
看 __closure__ 属性
每个闭包函数对象都有 __closure__ 属性,它是一个元组,存放所有被捕获的“自由变量”对应的 cell 对象。如果函数不是闭包,该属性为 None。
- 检查是否存在闭包:
func.__closure__ is not None - 查看捕获变量个数:
len(func.__closure__) - 逐个读取变量值:
func.__closure__[i].cell_contents
例如:
def make_adder(x):
return lambda y: x + y
add_10 = make_adder(10)
print(add_10.__closure__) # (<cell at 0x...: int object at 0x...>,)
print(add_10.__closure__[0].cell_contents) # 10识别自由变量名
仅知道值还不够,还需知道变量名。可用 __code__.co_freevars 获取自由变量名称元组:
-
add_10.__code__.co_freevars→('x',) - 它与
__closure__中的 cell 一一对应(按索引)
结合两者,就能还原出“x = 10”这个绑定关系。
注意变量是引用,不是拷贝
闭包捕获的是变量的引用,不是值的副本。如果外层变量是可变对象(如 list、dict),内层函数修改它,会影响所有共享该闭包的调用。
- 不可变对象(int/str/tuple):修改需用
nonlocal,否则赋值会创建新局部变量 - 可变对象(list/dict):直接调用方法(如
.append())会改变原对象,无需nonlocal
这解释了为什么有时多个闭包实例看似“共用状态”——本质是它们引用了同一个可变对象。
排查意外持留
闭包会阻止所引用变量被回收,若变量指向大型数据(如大列表、文件句柄、数据库连接),可能造成内存占用不降。
- 常见陷阱:在循环中创建闭包却复用同一变量(如
for i in range(3): funcs.append(lambda: i)) - 调试技巧:打印
sys.getrefcount(obj)看是否因闭包导致引用计数异常偏高 - 释放方式:显式删除闭包函数引用(
del func)或令其超出作用域

















