Python 闭包的经典坑

Python 闭包的经典坑

flyfish

闭包不是 包,是函数 + 它记住的变量,英文是 Closure,本意是封闭、闭合。

闭包是Python中的嵌套函数,满足 内部函数引用外部函数的非全局变量 + 外部函数返回内部函数对,这个内部函数就是闭包。

闭包保留外部函数的状态,让函数拥有记忆性,无需依赖全局变量就能记住历史数据

cpp 复制代码
def outer():
    name = "小明"   # 外部变量

    #  这个 inner 就是闭包
    def inner():
        print(name) # 用了外部的变量

    return inner

f = outer()
f()  # 依然能打印出小明

自由变量

python 复制代码
def outer(x):
    #  y:自由变量
    y = 10
    
    def inner():
        # 内部函数只用了 x 和 y,没有自己定义
        return x + y  # x、y 都是自由变量
    
    return inner

# 创建闭包
func = outer(5)
print(func())  # 15

x(外部函数参数)、y(外部函数变量)→ 都是自由变量

python 复制代码
def make_functions():
    func_list = []
    for i in range(3):
        #  i 就是自由变量
        def inner_func():
            return i  # 引用自由变量 i
        func_list.append(inner_func)
    return func_list

这里的 i 是自由变量,闭包(下面解释闭包)只记住了它的地址,没记住值,循环结束后i=2,所以所有闭包都返回2。

闭包:延迟绑定 vs 立即绑定

1. 什么是绑定?

闭包中,内部函数 和 外部自由变量 的关联过程 ,就叫绑定

2. 什么是延迟绑定?闭包默认行为 → 坑

Python 闭包的默认规则

定义函数时 → 不绑定变量的值 ,只记住变量的引用(地址)

调用函数时 → 才去查找变量的最终值

这就是延迟绑定问题,也是闭包最大的坑。

3. 什么是立即绑」?(解决方案 → 正确)

定义函数时 → 立刻锁死变量的当前值

调用函数时 → 直接用锁死的值,不会变

这是解决延迟绑定坑。

错误示例:默认延迟绑定(坑)

需求

循环创建3个函数,分别返回 0、1、2

问题

延迟绑定 → 所有函数都返回循环的最终值 2

完整可运行代码

python 复制代码
# 错误代码:闭包默认延迟绑定
def create_functions():
    # 存放函数的列表
    func_list = []
    
    # 循环创建3个闭包函数
    for i in range(3):
        # 定义内部函数(闭包)
        def inner():
            # 引用自由变量 i
            return i
        # 把函数添加到列表
        func_list.append(inner)
    
    # 返回所有函数
    return func_list

# ========== 测试(直接运行) ==========
if __name__ == "__main__":
    # 获取3个函数
    funcs = create_functions()
    # 调用函数
    print(funcs[0]())  # 预期0,实际2 
    print(funcs[1]())  # 预期1,实际2 
    print(funcs[2]())  # 预期2,实际2 

错误原因

  1. 循环时,inner 函数只记住了变量 i 的引用,没记住值
  2. 循环结束后,i 的最终值是 2
  3. 调用函数时,才去查 i 的值 → 全是 2

正确示例1:函数默认参数(立即绑定)

原理

Python 函数的默认参数 ,会在定义函数时立即绑定值,永久锁死。

完整可运行代码

python 复制代码
# 正确代码:默认参数实现立即绑定
def create_functions():
    func_list = []
    
    for i in range(3):
        # 关键:num=i → 定义时立即绑定当前i的值
        def inner(num=i):
            return num
        func_list.append(inner)
    
    return func_list

# ========== 测试(直接运行) ==========
if __name__ == "__main__":
    funcs = create_functions()
    print(funcs[0]())  # 0 
    print(funcs[1]())  # 1 
    print(funcs[2]())  # 2 

正确示例2:嵌套闭包(立即绑定)

原理

嵌套一层函数,立即传入循环变量,为每个函数创建独立的作用域,锁死值。

完整可运行代码

python 复制代码
# 正确代码:嵌套闭包实现立即绑定
def create_functions():
    func_list = []
    
    # 嵌套一层:接收值,立即绑定
    def wrapper(value):
        def inner():
            return value
        return inner
    
    # 循环时立即把i传入wrapper,锁死当前值
    for i in range(3):
        func_list.append(wrapper(i))
    
    return func_list

# ========== 测试(直接运行) ==========
if __name__ == "__main__":
    funcs = create_functions()
    print(funcs[0]())  # 0 
    print(funcs[1]())  # 1 
    print(funcs[2]())  # 2 

对比

绑定类型 触发时机 变量取值 结果
延迟绑定(默认) 函数调用时 取变量最终值 错误
立即绑定(解决方案) 函数定义时 取变量当前值(锁死) 正确

使用lambda省代码

cpp 复制代码
def create_functions():
    func_list = []
    for i in range(3):
        # lambda: num=i → 定义时立即绑定当前 i 的值
        func_list.append(lambda num=i: num)
    return func_list

# 测试(直接运行)
if __name__ == "__main__":
    funcs = create_functions()
    print(funcs[0]())  # 0 
    print(funcs[1]())  # 1 
    print(funcs[2]())  # 2

造成闭包经典坑的原因:

闭包不会保存自由变量的值,只会保存变量的引用(内存地址);

Python 循环会复用同一个变量,且函数默认延迟绑定(调用时才取值),最终所有闭包都读取到了循环结束后的最终值。

相关推荐
顾林海10 小时前
Agent入门阶段-编程基础-Python:流程控制
python·agent·ai编程
呱呱复呱呱13 小时前
Django CBV 源码解读:一个请求是怎么找到你的 get() 方法的
python·django
曲幽17 小时前
刚部署的 LibreTranslate 频频翻车?我掏出了 20 年前的 StarDict 词典,用 FastAPI 搭了个本地词典翻译 API
python·fastapi·web·translate·goldendict·libretranslate·stardict·pystardict
荣码18 小时前
用Streamlit给AI应用套个界面,10行代码出Web页面
java·python
兵慌码乱1 天前
基于Python+PyQt5+SQLite的药房管理系统实现:事务一致性与界面解耦全流程解析
python·sqlite·信号与槽·pyqt5·数据库设计·桌面应用开发·事务处理
金銀銅鐵1 天前
[Python] 体验用欧几里得算法计算最大公约数的过程
python·数学
FreakStudio1 天前
W55MH32L-EVB 上手测评:硬件 TCP/IP 加持的以太网单片机,MicroPython 零门槛开发
python·单片机·嵌入式·大学生·面向对象·并行计算·电子diy·电子计算机
用户0332126663671 天前
使用 Python 从零创建 Word 文档
python
Csvn2 天前
Python 两大经典坑点 —— 可变默认参数 & 闭包延迟绑定
后端·python
曲幽2 天前
别再用网页翻译看源码了!你的私人翻译神器LibreTranslate,部署避坑指南来了
python·docker·web·pot·translate·libretranslate·arogstranslate