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

相关推荐
u0110225126 分钟前
如何自定义查询历史记录面板的展示风格_时间轴样式设计
jvm·数据库·python
2301_769340678 分钟前
HTML怎么实现快捷跳转顶部_HTML固定悬浮锚点按钮【介绍】
jvm·数据库·python
yuanpan17 分钟前
Python + PyAutoGUI 实战:Windows 自动化办公脚本开发入门
windows·python·自动化
m0_6091604920 分钟前
MySQL如何限制触发器递归调用的深度_防止触发器死循环方法
jvm·数据库·python
zjy2777727 分钟前
Golang bcrypt如何加密密码_Golang密码加密教程【收藏】
jvm·数据库·python
老纪41 分钟前
Redis怎样利用Lua为多个Key同步续期
jvm·数据库·python
2403_8832610944 分钟前
C#怎么计算两个日期的差值_C#如何处理时间跨度【笔记】
jvm·数据库·python
m0_740653221 小时前
Golang切片底层原理是怎样的_Golang切片实现原理教程【简明】
jvm·数据库·python
yexuhgu1 小时前
CSS如何处理CSS逻辑属性兼容性_通过PostCSS转译为物理属性
jvm·数据库·python
m0_624578591 小时前
CSS如何给Bootstrap背景添加半透明层_使用rgba颜色模式与定位
jvm·数据库·python