闭包
"外层虽逝去,Cell 永长存" — 外层函数的局部变量通过 Cell 对象在闭包中延续生命。 ---ShyNi·Shit-
在编程中,闭包 是一个函数与其相关的 引用环境(即它被定义时所处的词法作用域)组合而成的一个 整体。
起:定义“问题”
先看一段代码:
# -*- coding: UTF-8 -*-
"""
@File : closure_test.py
@Time : 2026/2/28
@Author : chengyuai_tian@163.com
@Version : 1.0
@DESC : 闭包测试
"""
from typing import Callable
def exterior():
var: int = 1
def interior():
print(f"interior 访问 var: {var}")
return var
return interior
inter: Callable = exterior()
print(inter())
从上述代码中,我们大致能看到:
-
exterior 执行后(即:
exterior())的结果返回了interior对象。 -
interior 对象内部 return 了 var,但是 var 并未在 interior 作用域内部,而在 exterior 内部,interior 的外部。
我们都知道:如果一个函数执行完成后,其内部变量会销毁,是的,这是一般情况! 但是该结论对于上述代码而言,貌似不成立,不妨我们运行看一下,结果是什么:
#$:python closure_test.py
interior 访问 var: 1
1
我们分析一下最后两行代码:
inter: Callable = exterior() # 标记:A
print(inter()) # 标记:B
-
A 语句在执行了
exterior()后,按照一般情况,exterior 函数内部的变量应该会被销毁,显然在当前代码中,这句话不成立!随之而来的一个问题:inter拿到的 var 是哪里的?是 exterior 的 var 吗?或者 python 帮我们拷贝了一份 var 放到了 inter?** -
B 语句输出了 inter() 的结果
承:闭包内部逻辑
对于上面的问题:我们可以看一看 exterior 的字节码:
# -*- coding: UTF-8 -*-
"""
@File : closure_test.py
@Time : 2026/2/28
@Author : chengyuai_tian@163.com
@Version : 1.0
@DESC : 闭包测试
"""
from dis import dis
from typing import Callable
def exterior():
var: int = 1
def interior():
print(f"interior 访问 var: {var}")
return var
return interior
inter: Callable = exterior()
print(inter())
print("====== 字节码:exterior ======")
print(dis(exterior))
输出如下:
interior 访问 var: 1
1
====== 字节码:exterior ======
-- MAKE_CELL 1 (var)
13 RESUME 0
14 LOAD_CONST 1 (1)
STORE_DEREF 1 (var)
16 LOAD_FAST 1 (var)
BUILD_TUPLE 1
LOAD_CONST 2 (<code object interior at 0x00000213B1B9BD20, file "/Project/closure_test.py", line 16>)
MAKE_FUNCTION
SET_FUNCTION_ATTRIBUTE 8 (closure)
STORE_FAST 0 (interior)
20 LOAD_FAST 0 (interior)
RETURN_VALUE
Disassembly of <code object interior at 0x00000213B1B9BD20, file "/Project/closure_test.py", line 16>:
-- COPY_FREE_VARS 1
16 RESUME 0
17 LOAD_GLOBAL 1 (print + NULL)
LOAD_CONST 1 ('interior 访问 var: ')
LOAD_DEREF 0 (var)
FORMAT_SIMPLE
BUILD_STRING 2
CALL 1
POP_TOP
18 LOAD_DEREF 0 (var)
RETURN_VALUE
None
闭包执行流程(自然语言版)
第一阶段:定义外层函数
当 Python 解析器读到 exterior 函数定义时,它只是"记住"了这个函数的结构,并不执行任何代码。
第二阶段:调用 exterior()
-
进入函数 — Python 为
exterior创建一个新的栈帧,分配局部变量空间 -
准备闭包容器 — 看到内层函数
interior引用了var,Python 说:"这个变量以后会被别人用,我不能把它放在普通变量槽里,要放在一个特殊的共享容器(Cell)中"- 这就是
MAKE_CELL做的事
- 这就是
-
赋值 —
var = 1执行,数字1被放入 Cell 容器中 -
定义内层函数 — 创建
interior函数时,Python 告诉它:"你需要用var变量,它的地址在 Cell 容器里,记下来" -
返回函数 —
exterior结束,它的栈帧本该销毁,但 Cell 容器不能销毁,因为interior还要用它
第三阶段:调用 interior()
inter = exterior() # 此时 exterior 已结束,但 var 仍"活着"
inter() # 调用返回的闭包
-
启动闭包 —
interior函数被调用,它从自己的"行李箱"(__closure__)里找到 var 的引用 -
访问变量 —
print(var)时,通过引用找到 Cell 容器,取出里面的值1 -
返回值 — 同样通过引用,返回
1
核心要点
外层函数虽已消逝,Cell 容器让变量长存
exterior() 执行完毕
│
▼
栈帧 normally 应该销毁
│
│ 但!var 被闭包引用
▼
Cell 容器被保留
│
▼
interior 通过 Cell 继续访问 var
这就是为什么闭包能"记住"外层变量的值——因为它保存的不是变量的值副本,而是变量存储位置(Cell)的引用。
转:携带参数的闭包
携带参数的闭包,我们更常定义为:函数工厂。携带参数的闭包 == 参数工厂
外层函数接收参数,内层函数"记住"这些参数,以后调用时直接使用。
基本形式
def make_multiplier(factor): # 参数在这里传入
def multiply(x):
return x * factor # factor 被闭包"记住"
return multiply
times3 = make_multiplier(3) # factor = 3 被携带
times5 = make_multiplier(5) # factor = 5 被携带
print(times3(10)) # 30
print(times5(10)) # 50
常用场景
1. 配置预设
def make_greeter(greeting, punctuation):
def greet(name):
return f"{greeting}, {name}{punctuation}"
return greet
hello = make_greeter("Hello", "!")
hi = make_greeter("Hi", "...")
print(hello("World")) # Hello, World!
print(hi("World")) # Hi, World...
2. URL构建器
想一想 Flask 或者 FastApi 中的路由,会不会用到这个点呢?
def base_url(domain):
def build(path):
return f"https://{domain}{path}"
return build
github = base_url("github.com")
google = base_url("google.com")
print(github("/user")) # https://github.com/user
print(google("/search")) # https://google.com/search
3. 验证器工厂
def make_validator(min_val, max_val):
def is_valid(x):
return min_val <= x <= max_val
return is_valid
is_age = make_validator(0, 150)
is_rating = make_validator(1, 5)
print(is_age(25)) # True
print(is_age(200)) # False
print(is_rating(3)) # True
print(is_rating(6)) # False
4. 带默认参数的函数
def make_request(base_headers):
def request(url, extra_headers=None):
headers = {**base_headers, **(extra_headers or {})}
# 发送请求...
return f"GET {url} with {headers}"
return request
api_request = make_request({"Auth": "token123", "User-Agent": "MyApp"})
print(api_request("/users"))
# GET /users with {'Auth': 'token123', 'User-Agent': 'MyApp'}
5. 带默认参数的函数
def make_power(exponent):
def power(base):
return base ** exponent
return power
square = make_power(2) # 携带 exponent=2
cube = make_power(3) # 携带 exponent=3
print(square(4)) # 16
print(cube(4)) # 64
合
# 通用公式
def 外部(外部参数们):
def 实际函数(运行时参数):
使用(外部的参数, 运行时参数)
return 结果
return 实际函数
你采取的每个行动都像一张选票,投给你想要成为的那一种人。 ---詹姆斯·克利尔-

浙公网安备 33010602011771号