深入理解 Python 闭包:从基础到高级应用
作为一名数仓工程师,我平时使用 Python 的场景主要集中在脚本开发和数据处理。
直到有一次被问到“什么是闭包”时,我才意识到自己对这个概念的理解其实并不深入。
于是我整理了这篇文章,希望用清晰的语言和示例,带大家真正弄懂闭包的原理和应用。
一、什么是闭包
在 Python 中,闭包(Closure)是指:
一个函数(内部函数)定义在另一个函数(外部函数)内部,并且内部函数引用了外部函数的变量。
当外部函数返回内部函数时,这个内部函数就形成了闭包。闭包能够“记住”它被创建时所在的作用域环境。
这里的关键是“词法作用域”。
词法作用域是指函数在定义时(而不是运行时)决定变量可见性的规则。
闭包之所以能在外部函数执行完后仍然访问外部变量,就是因为它保存了定义时的作用域环境。
二、闭包的三要素
- 嵌套函数:在一个函数内部定义另一个函数
- 变量引用:内部函数引用了外部函数的变量
- 函数返回:外部函数返回内部函数
三、闭包的基本示例
def outer_function(outer_var):
"""外部函数"""
def inner_function(inner_var):
"""内部函数 - 闭包"""
return f"外部变量: {outer_var}, 内部变量: {inner_var}"
return inner_function
closure = outer_function("外部值")
print(closure("内部值")) # 输出: 外部变量: 外部值, 内部变量: 内部值
print(closure("另一个内部值")) # 输出: 外部变量: 外部值, 内部变量: 另一个内部值

在这个例子中,inner_function 就是一个闭包,它记住了 outer_var 的值。
即使 outer_function 已经执行完毕,闭包依然能访问到这个外部变量。
四、闭包的内部机制
闭包在 Python 中其实是一种函数对象,它通过 __closure__ 属性保存被引用的变量。
def counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
c1 = counter()
c2 = counter()
print(c1()) # 1
print(c1()) # 2
print(c2()) # 1
print(c1()) # 3
print(c1.__closure__)
print([cell.cell_contents for cell in c1.__closure__])

__closure__ 是一个包含外部变量的元组。
通过它,我们可以看到闭包内部真正保存了哪些数据。
这里的 nonlocal 关键字用来声明 count 不是局部变量,从而允许在内部函数中修改外部函数作用域的变量。
五、循环陷阱(延迟绑定问题)
闭包的一个常见陷阱是“延迟绑定”。
看下面的例子:
def create_functions():
functions = []
for i in range(3):
def func():
return i
functions.append(func)
return functions
funcs = create_functions()
for f in funcs:
print(f()) # 输出 2, 2, 2

问题的原因是闭包捕获的是变量的“引用”,而不是定义时的“值”。
循环结束后,i 的值是 2,所以所有闭包返回的都是 2。
解决方案:默认参数捕获值
def create_functions_fixed():
functions = []
for i in range(3):
def func(i=i):
return i
functions.append(func)
return functions

不细看真看不出区别,但是结果却完全不一样,这是因为闭包中变量的绑定时机的不同。
在第一个函数中:
闭包捕获的是“变量的引用”,而不是当时的值。
所以三个函数都指向同一个 i,最终它的值变成了 2。
在第二个函数中:
默认参数 i=i 会在函数定义时求值(定义那一刻执行赋值),
所以每次循环时,func 都拿到了不同的 i 值,形成了独立的绑定。
六、闭包的常见应用
1. 装饰器
def logger(func):
def wrapper(*args, **kwargs):
print(f"调用函数: {func.__name__}")
result = func(*args, **kwargs)
print(f"函数 {func.__name__} 执行完毕")
return result
return wrapper
@logger
def add(a, b):
return a + b
print(add(2, 3))
装饰器的本质其实就是闭包。
2. 配置函数
def create_multiplier(factor):
def multiply(x):
return x * factor
return multiply
double = create_multiplier(2)
triple = create_multiplier(3)
print(double(5))
print(triple(5))
3. 数据隐藏与封装
def create_bank_account(initial_balance=0):
balance = initial_balance
def deposit(amount):
nonlocal balance
balance += amount
return balance
def withdraw(amount):
nonlocal balance
if amount <= balance:
balance -= amount
return balance
else:
return "余额不足"
def get_balance():
return balance
return {
'deposit': deposit,
'withdraw': withdraw,
'get_balance': get_balance
}
account = create_bank_account(100)
print(account['get_balance']())
print(account )
print(account )
闭包可以让数据在函数外部不可见,从而实现类似“私有变量”的效果。
4. 回调函数
def create_event_handler(button_name):
def handle_event(event):
print(f"按钮 {button_name} 被点击,事件: {event}")
return handle_event
button1 = create_event_handler("确定")
button2 = create_event_handler("取消")
button1("click")
button2("click")
七、闭包与内存管理
闭包会保持对外部变量的引用,如果引用的是大对象,可能会导致内存无法及时释放。
import sys
def test_memory():
def outer():
large_data = "x" * 1000000
def inner():
return len(large_data)
return inner
closure = outer()
print(sys.getsizeof(closure))
print([cell.cell_contents for cell in closure.__closure__])
del outer
print("闭包仍然可用:", closure())
test_memory()
需要注意:sys.getsizeof() 只能得到函数对象本身的大小,不包括被引用对象的内存。
八、闭包的优缺点
优点:
- 数据隐藏,防止外部修改
- 函数能记住状态,实现状态持久化
- 提高代码复用性
- 是装饰器等高级特性的基础
缺点:
- 可能造成内存占用
- 调试不直观
- 容易出现延迟绑定问题
九、总结
闭包是 Python 中非常有用的特性,它让函数具备了“记忆”能力。
掌握闭包的关键在于理解三点:
- 闭包由嵌套函数、变量引用、返回函数三部分组成
- 闭包保存的是变量的引用,而非值
- 装饰器、工厂函数、状态保持等场景都离不开闭包
更多推荐
所有评论(0)