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

相关推荐
小溪学编程2 分钟前
Java InputStream 详解:从基础到实战
java·python·php
笨笨饿44 分钟前
#137_死机恢复现场_Armino平台AP系统swd调试
git·python·stm32·单片机·嵌入式硬件·物联网·vim
2601_962382431 小时前
python常用函数大全pdf-python函数大全.pdf
python·内置函数·io操作·集合操作·数学运算
奈斯先生Vector1 小时前
AIGC 视频生成实战:用 Kling Video 拆解文生视频、图生视频与完整工作流
开发语言·人工智能·windows·python·aigc·音视频
小玮看世界1 小时前
[Python]螺旋遍历 vs 最短路径:方向控制类算法的“同源异流”
开发语言·python·算法
OPEN-F1 小时前
ROS2系列教程:tf2坐标变换详解(C++/Python)
开发语言·c++·python
2601_962097362 小时前
Python工作流实战:SpiffWorkflow深度应用与BPMN自动化指南
python·自动化·工作流·bpmn·spiffworkflow
baopixiaoz2 小时前
BeeQuant × BeeAgent:AI驱动智能交易
大数据·人工智能·python·区块链
公爵爱学习2 小时前
无人机定点飞行-介绍
开发语言·图像处理·python·学习·线性回归·无人机
stillstream_ink2 小时前
OpenCart UI 自动化测试实战:POM 四层架构 + 失败自动截图 + 飞书通知
python·测试