Skip to content

闭包:捕获外层变量与生命周期 ​

核心概念 ​

闭包(closure)是捕获了外层作用域变量的函数。即使外层函数已经返回,闭包仍然可以访问和修改这些变量。

关键特征:

  • 嵌套函数
  • 内层函数引用外层函数的变量
  • 外层函数返回内层函数
  • 被捕获的变量在外层函数返回后仍然存活

第一个闭包 ​

python
def make_multiplier(n):
    def multiplier(x):
        return x * n  # 引用外层的 n
    return multiplier

times3 = make_multiplier(3)
times5 = make_multiplier(5)

print(times3(10))  # 30
print(times5(10))  # 50

解释:

  • make_multiplier(3) 返回 multiplier 函数
  • multiplier 函数"记住"了 n = 3
  • 每次调用 times3,都可以访问它创建时的 n 值
  • times3 和 times5 捕获的是不同的 n

闭包的内部机制 ​

Python 将闭包捕获的变量存储在函数对象的 __closure__ 属性中:

python
def make_counter():
    count = 0
    
    def counter():
        nonlocal count
        count += 1
        return count
    
    return counter

c = make_counter()
print(c())  # 1
print(c())  # 2

# 查看闭包
print(c.__closure__)  # (<cell at 0x...: int object at 0x...>,)
print(c.__closure__[0].cell_contents)  # 2

__closure__ 是一个元组,包含捕获变量的 cell 对象。cell_contents 是实际值。

闭包捕获变量,不是值 ​

python
def make_functions():
    funcs = []
    for i in range(3):
        funcs.append(lambda: i)  # 捕获变量 i,不是值
    return funcs

f0, f1, f2 = make_functions()
print(f0())  # 2(不是 0!)
print(f1())  # 2
print(f2())  # 2

问题:所有 lambda 捕获的是同一个变量 i,循环结束时 i = 2,所有函数返回 2。

修复方法 1:使用默认参数立即绑定值

python
def make_functions():
    funcs = []
    for i in range(3):
        funcs.append(lambda x=i: x)  # x 在定义时绑定当前 i 的值
    return funcs

f0, f1, f2 = make_functions()
print(f0())  # 0
print(f1())  # 1
print(f2())  # 2

修复方法 2:使用额外的闭包层

python
def make_functions():
    def make_func(n):
        return lambda: n
    
    return [make_func(i) for i in range(3)]

f0, f1, f2 = make_functions()
print(f0())  # 0
print(f1())  # 1
print(f2())  # 2

闭包的实际应用 ​

1. 工厂函数 ​

创建配置不同的函数:

python
def make_power(exponent):
    def power(base):
        return base ** exponent
    return power

square = make_power(2)
cube = make_power(3)

print(square(5))  # 25
print(cube(5))   # 125

2. 数据封装(隐藏状态) ​

python
def make_account(initial_balance):
    balance = initial_balance  # 私有变量
    
    def deposit(amount):
        nonlocal balance
        balance += amount
        return balance
    
    def withdraw(amount):
        nonlocal balance
        if amount > balance:
            return "Insufficient funds"
        balance -= amount
        return balance
    
    def get_balance():
        return balance
    
    return {'deposit': deposit, 'withdraw': withdraw, 'balance': get_balance}

account = make_account(100)
print(account['deposit'](50))   # 150
print(account['withdraw'](30))  # 120
print(account['balance']())     # 120

外部无法直接访问 balance,只能通过返回的函数接口。

3. 延迟计算 ​

python
def make_averager():
    series = []
    
    def averager(new_value):
        series.append(new_value)
        return sum(series) / len(series)
    
    return averager

avg = make_averager()
print(avg(10))  # 10.0
print(avg(11))  # 10.5
print(avg(12))  # 11.0

series 在多次调用之间保持状态。

4. 回调函数 ​

python
def make_handler(prefix):
    def handler(message):
        print(f"{prefix}: {message}")
    return handler

error_handler = make_handler("ERROR")
info_handler = make_handler("INFO")

error_handler("File not found")  # ERROR: File not found
info_handler("Server started")   # INFO: Server started

闭包 vs 类 ​

闭包和类可以实现类似功能:

闭包版本:

python
def make_counter():
    count = 0
    
    def increment():
        nonlocal count
        count += 1
        return count
    
    return increment

counter = make_counter()
print(counter())  # 1
print(counter())  # 2

类版本:

python
class Counter:
    def __init__(self):
        self.count = 0
    
    def increment(self):
        self.count += 1
        return self.count

counter = Counter()
print(counter.increment())  # 1
print(counter.increment())  # 2

何时用闭包:

  • 简单的状态封装
  • 只有一两个函数
  • 函数式编程风格

何时用类:

  • 复杂的数据和行为
  • 多个方法
  • 需要继承或多态

闭包与垃圾回收 ​

闭包捕获的变量只要闭包存在就不会被回收:

python
def outer():
    large_data = [0] * 1000000  # 大对象
    
    def inner():
        return large_data[0]
    
    return inner

func = outer()  # large_data 不会被回收
print(func())   # 0

del func  # 现在 large_data 可以被回收

注意:如果不需要捕获大对象,避免在内层函数中引用它。

nonlocal 详解 ​

nonlocal 用于修改闭包捕获的变量:

python
def make_counter():
    count = 0
    
    def increment():
        nonlocal count  # 声明修改外层 count
        count += 1
        return count
    
    def reset():
        nonlocal count
        count = 0
    
    return increment, reset

inc, rst = make_counter()
print(inc())  # 1
print(inc())  # 2
rst()
print(inc())  # 1

nonlocal 的规则:

  • 只能用于嵌套函数
  • 查找最近的外层作用域(不包括全局)
  • 如果找不到,报 SyntaxError

nonlocal vs global:

python
count = 0  # 全局

def outer():
    count = 10  # 外层
    
    def use_global():
        global count
        count += 1
        return count
    
    def use_nonlocal():
        nonlocal count
        count += 1
        return count
    
    print(use_global())   # 1(修改全局 count)
    print(use_nonlocal()) # 11(修改外层 count)
    print(f"Outer count: {count}")  # 11

outer()
print(f"Global count: {count}")  # 1

练习 ​

  1. 温度转换器工厂:编写 make_converter(from_unit, to_unit),返回温度转换函数。支持 Celsius 和 Fahrenheit 互转。

    python
    c_to_f = make_converter('C', 'F')
    print(c_to_f(0))   # 32.0
    print(c_to_f(100)) # 212.0
  2. 计数器三件套:编写 make_counter(),返回三个函数:increment(), decrement(), value()。

    python
    inc, dec, val = make_counter()
    inc()
    inc()
    dec()
    print(val())  # 1
  3. 修复闭包陷阱:解释并修复以下代码:

    python
    def make_multipliers():
        return [lambda x: x * i for i in range(5)]
    
    funcs = make_multipliers()
    print([f(2) for f in funcs])  # 期望 [0, 2, 4, 6, 8],实际输出什么?
  4. 累加器:用闭包实现累加器,支持 add(n) 和 total() 操作:

    python
    acc = make_accumulator()
    acc['add'](10)
    acc['add'](20)
    acc['add'](30)
    print(acc['total']())  # 60
  5. 限流器:编写 make_rate_limiter(max_calls, period),返回装饰器函数,限制函数在 period 秒内最多调用 max_calls 次。

完成练习后,打开本章答案页核对。

下一步 ​

理解了闭包,下一节深入参数模型:*args、**kwargs、仅限关键字参数、参数解包的完整规则。

回到第二章概览 · 返回Python 学习路线

用心记录每一步成长