闭包详解
1 Python 闭包的核心概念
闭包(Closure) 是一种函数嵌套的编程结构,指内部函数引用了外部函数的变量(自由变量),且外部函数返回内部函数的现象。此时内部函数会“捕获”并“记住”外部变量的状态,即使外部函数已执行完毕。
2 闭包的三个关键要素
- 函数嵌套:必须有一个外层函数和一个内层函数。
- 引用自由变量:内层函数必须使用外层函数定义的变量(非全局变量)。
- 返回内层函数:外层函数必须将内层函数作为返回值返回(不带括号)。
3 闭包必须返回内层函数吗?
3.1 核心结论
闭包的本质定义,并不强制要求必须返回内层函数;但「返回内层函数」是 Python 闭包的标准、主流用法,也是闭包发挥核心价值的典型形态。
3.2 详细拆解
1. 先明确闭包的本质:和「返回」无关,核心是「词法环境绑定」
在计算机科学的广义定义中,闭包 = 函数 + 该函数捕获的自由变量的词法环境。
它的核心特征是:内层函数会记住自己定义时的作用域,哪怕在定义作用域之外执行,依然能访问当时捕获的自由变量。
换句话说,只要满足两个核心条件,就已经形成了闭包:
- 存在函数嵌套(内层函数)
- 内层函数引用了外层函数作用域的非全局自由变量
是否返回内层函数,只决定这个闭包能不能在外部作用域被使用,不决定闭包本身是否存在。
2. Python 语言层面的判定:和「返回」无绑定关系
Python 中,判断一个函数是不是闭包,有明确的底层标准:
[!note]
函数对象的__closure__属性不为None,且包含存储自由变量的cell对象,即为闭包。
这个判定规则,完全不关心内层函数是否被外层函数返回。
示例 1:不返回内层函数,依然形成闭包(赋值给全局变量)
global_inner = None # 全局变量,用于接收内层函数
def outer():
# 外层函数的自由变量
msg = "我被闭包捕获了"
def inner():
# 内层函数引用外层变量,形成环境绑定
print(msg)
# 不return内层函数,而是赋值给全局变量
global global_inner
global_inner = inner
print(f"msg 的内存地址为 {id(msg):#x}")
# 执行外层函数,执行完毕后,外层作用域本应销毁
outer()
# 全局作用域调用内层函数,依然能访问到 msg
global_inner() # 输出:我被闭包捕获了
# 验证闭包属性:__closure__ 包含 cell 对象,证明是闭包
print(global_inner.__closure__) # 输出:(<cell at 0x...: str object at 0x...>,)

示例 2:不返回内层函数,依然形成闭包(作为回调注册)
# 不返回内层函数,依然形成闭包(作为回调注册)
import threading
def register_timer():
username = "张三"
# 内层回调函数,捕获外层变量 username
def callback():
print(f"定时回调执行,当前用户:{username}")
# 不 return 回调函数,直接注册到定时器,交给其他线程持有
threading.Timer(1.0, callback).start()
# 执行注册函数,执行完毕后外层作用域销毁
register_timer()
# 1 秒后,回调函数依然能正常访问 username,闭包生效

示例 3:极端情况:内层函数仅在外层内部调用,不导出,依然是闭包
# 极端情况:内层函数仅在外层内部调用,不导出,依然是闭包
def outer():
num = 10
def inner():
print(num * 2)
# 直接在内层调用,不返回、不导出到外部
inner()
# 这里 inner 函数的__closure__已经有 cell 对象,本质就是闭包
print(inner.__closure__) # 输出非 None 的 cell 元组
outer()

这种场景下,闭包确实形成了,只是生命周期和外层函数绑定,没有在外部发挥作用而已。
3. 为什么几乎所有教程都把「返回内层函数」作为闭包的核心要素?
这是实用主义的简化定义,而非严格的语法规则,原因有 3 点:
- 闭包的核心价值,只有导出内层函数才能体现:闭包最大的作用是「封装私有状态、在定义作用域外保留变量生命周期」,如果不把内层函数导出(返回、赋值给外部变量、注册回调等),它只能在外层函数内部使用,闭包的能力完全无法被外部利用,没有实用价值。
- 返回是最简洁、最通用的导出方式:相比赋值给全局变量、注册回调等方式,return 内层函数是无副作用、最通用的闭包使用方式,也是装饰器等 Python 核心语法的底层基础。
- 面试/学习的标准化要求:在 Python 的入门教学、面试考察中,默认考察的都是「可复用的标准闭包」,因此会把「返回内层函数」作为闭包的标志性特征。
4. 关键误区纠正
-
❌ 误区 1:只要返回内层函数,就是闭包
正解:如果内层函数没有引用任何外层自由变量,哪怕被返回,也不是闭包。
def outer(): def inner(): print("hello") return inner f = outer() print(f.__closure__) # 输出 None,不是闭包 -
❌ 误区 2:只有返回内层函数,外层变量才不会被销毁
正解:只要内层函数被任意对象持有(全局变量、回调、容器等),它的
__closure__就会持有自由变量的引用,变量就不会被垃圾回收,和是否 return 无关。
3.3 最终总结
| 维度 | 是否必须返回内层函数 |
|---|---|
| 闭包的本质定义(计算机科学) | 否,核心是「函数 + 捕获的词法环境」 |
Python 底层闭包判定(__closure__) |
否,和是否返回无绑定关系 |
| Python 闭包的标准/主流用法 | 是,返回是最通用、最有价值的使用方式 |
| 入门学习/面试考察的标准形态 | 是,默认考察带返回的闭包实现 |
4 简单示例:计数器
def create_counter():
count = 0 # 外层函数的局部变量(自由变量)
def counter():
nonlocal count # 声明使用外层变量(Python 3 需加)
count += 1
return count
return counter # 返回内层函数,不执行
# 使用闭包
c1 = create_counter()
print(c1()) # 输出: 1
print(c1()) # 输出: 2(状态被保留)
c2 = create_counter() # 新的闭包实例,独立状态
print(c2()) # 输出: 1
5 闭包的核心作用
- 封装私有状态:模拟“私有变量”,避免全局变量污染。
- 延迟执行:保存上下文,待后续调用时再执行。
- 装饰器基础:Python 装饰器的本质就是闭包的应用。
6 注意事项
- 若需修改外层变量,Python3 中必须用
nonlocal声明(Python2 无此关键字,需用可变对象如列表模拟)。 - 闭包会延长外层变量的生命周期,可能导致内存占用,需合理使用。

浙公网安备 33010602011771号