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

相关推荐
西西弗Sisyphus2 小时前
Python 在dataclasses 里,field() 能给可变、不可变数据分别设置安全的默认值
python·field·dataclasses
西西弗Sisyphus2 小时前
Python @dataclass 有 `__post_init__` 和 无 `__post_init__` 的对比
python·dataclass·__post_init__
独隅2 小时前
PyCharm 开启硬换行的方法
ide·python·pycharm
weixin_408099673 小时前
python请求文字识别ocr api
开发语言·人工智能·后端·python·ocr·api·ocr文字识别
我会好好吃饭歌3 小时前
医疗单据隐私脱敏开源项目:OCR + Vision LLM + 四点定位打码,适配弯曲、旋转、复杂拍摄场景
图像处理·python·开源项目·paddleocr·医疗ai·隐私脱敏
惊鸿若梦一书生3 小时前
《Python 高阶教程》003|变量背后不是盒子:名字、对象与引用的本质
java·jvm·python
qq_380619163 小时前
SQL中如何实现特定范围内数据的批量删除_范围分区与分区删除
jvm·数据库·python
Hommy883 小时前
【开源剪映小助手】云渲染环境搭建
python·开源·github·剪映小助手
qq_380619163 小时前
HTML函数开发需要独立显卡吗_HTML函数与显卡关系详解【说明】
jvm·数据库·python