当前位置:首页>python>python:彻底搞懂Python闭包(附代码)

python:彻底搞懂Python闭包(附代码)

  • 2026-10-11 08:00:38
python:彻底搞懂Python闭包(附代码)
很多Python学习者对“闭包”一词既熟悉又陌生:能背出定义,但一写代码就迷糊。今天我们就用最接地气的方式,把它从底层原理到实战用法彻底讲透。

读完你会清楚:

  • __closure__ 到底是个什么“元组”?

  • 为什么调用10次外层函数,能得到10个互不干扰的闭包?

  • 闭包如何实现“记住状态”“定制函数”“隐藏数据”?

  • 为什么有时候类+实例属性,反而比闭包更合适?

1. 什么是闭包

闭包 = 内层函数 + 被内层函数所引用的外层变量

只要满足三个条件,闭包就诞生了:

  1. 函数嵌套:一个函数内部定义了另一个函数

  2. 内层引用:内层函数使用了外层函数的变量(或参数)

  3. 外层返回:外层函数把内层函数作为返回值交出来

只有把内层函数交出去,那些被引用的外层变量才不会随着外层函数的结束而消亡,闭包才真正“活”下来。

2. closure 元组与闭包单元

我们说的“闭包”,在Python内部其实是一个元组。
def out(x):    y = 10    def inner():        return x + y    return innerf = out(5)print(f.__closure__)  # 输出类似:(<cell at 0x10...: int object at 0x...>, <cell at 0x...: int object at 0x...>)print(len(f.__closure__))        # 2print(f.__closure__[0].cell_contents)  # 5print(f.__closure__[1].cell_contents)  # 10

解读:

  • out函数中,被inner用到的变量 x 和 y,会被封存在闭包单元(cell)里。

  • 这些cell会组成一个__closure__元组,最终挂在内层函数 inner 身上。

  • 每个cell可以通过 .cell_contents 查看当前保存的值。

这就解释了为什么外层函数执行完了,变量还能“活着”——它们其实被搬到了内层函数的__closure__里。

小提示:如果内层函数没有引用任何外层变量,它的 __closure__ 就是 None。

3. 两个重要特性

特性1:调用N次外层函数,得到N个互不影响的闭包

每次调用外层函数,都会创建一套全新的局部变量,所以闭包之间的状态是隔离的。

def counter(start=0):    count = start    def inc():        nonlocal count        count += 1        return count    return incc1 = counter(10)c2 = counter(10)print(c1())  # 11print(c1())  # 12print(c2())  # 11   ← c2完全不受c1影响
c1 和 c2 各自拥有自己的 count 变量,就像两个独立的计数器。

特性2:外层变量是可变对象,闭包之间依然互不影响

就算捕获的是列表、字典等可变对象,只要它们来自不同的外层函数调用,闭包之间也是隔离的。

def recorder():    log = []       # 可变对象    def add(item):        log.append(item)        return log    return addr1 = recorder()r2 = recorder()print(r1('a'))   # ['a']print(r1('b'))   # ['a', 'b']print(r2('x'))   # ['x']   ← r2有自己的log列表,完全独立

关键点:隔离的本质是因为每次调用 recorder() 都生成了全新的 log 列表。只要不是同一个闭包实例多次修改同一个可变对象,它们就不会打架。

4. 闭包的四大优点

1. 记住状态:不用全局变量,也不用类

做游戏计数器、缓存、累加器时,闭包可以帮你“记住”上次的结果。

def make_accumulator():    total = 0    def add(n):        nonlocal total        total += n        return total    return addacc = make_accumulator()print(acc(5))   # 5print(acc(3))   # 8
没有全局变量污染,也没有写class,状态就保留下来了。

2. 定制“配置过的函数”:先传参,后使用

有些函数需要一部分参数固定不变,闭包可以帮你制造“高配版”函数。
def multiply_by(factor):    def multiply(number):        return number * factor    return multiplydouble = multiply_by(2)triple = multiply_by(3)print(double(5))  # 10print(triple(5))  # 15
double 就是一个被“配置”成永远乘2的函数。这个模式在回调、数据处理流水线中非常实用。

3. 实现简单的数据隐藏

外层变量对外界是不可见的,只能通过返回的内层函数间接操作,等于给数据加了一层保护。
def bank_account(initial):    balance = initial    def deposit(amount):        nonlocal balance        balance += amount        return balance    def withdraw(amount):        nonlocal balance        if amount <= balance:            balance -= amount            return balance        else:            return "余额不足"    return deposit, withdrawdep, wd = bank_account(100)print(dep(50))   # 150# 外部根本无法直接访问 balance 变量

4. 装饰器的基石

Python装饰器本质上就是闭包。理解了闭包,再看 @timer、@login_required 这类语法就会恍然大悟。

五、闭包的三个缺点(清醒使用)

  1. 理解成本高对团队里的新手不太友好,过度嵌套会让代码可读性急剧下降。

  2. 可能无意中占用大内存如果闭包捕获了一个巨大的对象(如大列表、大DataFrame),而且闭包长期驻留,这块内存就一直释放不了。

  3. 类+实例属性有时更清晰闭包能记住状态,一个类的实例也能。如果逻辑开始变复杂(多个方法、状态多),用类往往比多层闭包更容易维护。

# 同样的累加器,用类实现可能更直观class Accumulator:    def __init__(self):        self.total = 0    def add(self, n):        self.total += n        return self.total
没有万能方案,只有最适合当前场景的方案。

六、总结

  • 闭包 = 内层函数 + 外层被引用的变量

  • 灵魂是 __closure__ 元组,由一个个cell构成

  • 每次调用外层函数,都会生成独立的闭包环境

  • 优点:记住状态、定制函数、隐藏数据、成就装饰器

  • 注意:读不懂要重构,大对象慎入,复杂逻辑考虑用类

理解闭包,不只是为了应付面试,而是真正掌握Python函数式编程的一把钥匙。


最新文章

随机文章