闭包

"外层虽逝去,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())

从上述代码中,我们大致能看到:

  1. exterior 执行后(即:exterior())的结果返回了 interior 对象。

  2. 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()

  1. 进入函数 — Python 为 exterior 创建一个新的栈帧,分配局部变量空间

  2. 准备闭包容器 — 看到内层函数 interior 引用了 var,Python 说:"这个变量以后会被别人用,我不能把它放在普通变量槽里,要放在一个特殊的共享容器(Cell)中"

    • 这就是 MAKE_CELL 做的事
  3. 赋值var = 1 执行,数字 1 被放入 Cell 容器中

  4. 定义内层函数 — 创建 interior 函数时,Python 告诉它:"你需要用 var 变量,它的地址在 Cell 容器里,记下来"

  5. 返回函数exterior 结束,它的栈帧本该销毁,但 Cell 容器不能销毁,因为 interior 还要用它

第三阶段:调用 interior()

inter = exterior()  # 此时 exterior 已结束,但 var 仍"活着"
inter()             # 调用返回的闭包
  1. 启动闭包interior 函数被调用,它从自己的"行李箱"(__closure__)里找到 var 的引用

  2. 访问变量print(var) 时,通过引用找到 Cell 容器,取出里面的值 1

  3. 返回值 — 同样通过引用,返回 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 实际函数

你采取的每个行动都像一张选票,投给你想要成为的那一种人。 ---詹姆斯·克利尔-

posted @ 2026-02-28 14:29  shyNi  阅读(10)  评论(0)    收藏  举报