Skip to content

闭包

1. 闭包的概念

Python 中的闭包是指延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量。函数是不是匿名的没有关系,关键是它能访问定义体之外定义的非全局变量。

闭包是函数式编程的重要特性,它允许函数访问并操作函数外部的变量。当一个内部函数引用了外部函数的变量时,即使外部函数已经执行完毕,内部函数仍然可以访问这些变量。

python
def make_averager():
    series = []

    def averager(new_value):
        series.append(new_value)
        total = sum(series)
        return total / len(series)

    return averager

在这个例子中,averager 函数引用了外部函数 make_averager 的变量 series,即使 make_averager 已经返回,averager 仍然可以访问和修改 series

使用示例:

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

2. 闭包的应用场景

2.1 数据封装和隐藏

闭包可以用来创建私有变量,实现数据封装:

python
def counter():
    count = 0
    
    def increment():
        nonlocal count
        count += 1
        return count
    
    def decrement():
        nonlocal count
        count -= 1
        return count
    
    def get_count():
        return count
    
    return increment, decrement, get_count

inc, dec, get = counter()
print(inc())  # 1
print(inc())  # 2
print(dec())  # 1
print(get())  # 1

2.2 延迟计算和缓存

闭包可以用来实现延迟计算和缓存机制:

python
def memoize(func):
    cache = {}
    
    def wrapper(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    
    return wrapper

@memoize
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(100))  # 快速计算,因为结果被缓存

2.3 配置和工厂函数

闭包可以用来创建配置化的函数:

python
def make_multiplier(factor):
    def multiply(number):
        return number * factor
    return multiply

times_three = make_multiplier(3)
times_five = make_multiplier(5)

print(times_three(10))  # 30
print(times_five(10))   # 50

2.4 回调函数

闭包常用于创建带有特定上下文的回调函数:

python
def create_button_handler(button_id):
    def handler():
        print(f"Button {button_id} was clicked")
    return handler

button1_handler = create_button_handler(1)
button2_handler = create_button_handler(2)

button1_handler()  # Button 1 was clicked
button2_handler()  # Button 2 was clicked

3. nonlocal 关键字

在闭包中修改外部变量时,需要使用 nonlocal 关键字声明变量:

python
def outer():
    count = 0
    
    def inner():
        nonlocal count  # 声明 count 是外部变量
        count += 1
        return count
    
    return inner

counter = outer()
print(counter())  # 1
print(counter())  # 2
print(counter())  # 3

如果不使用 nonlocal,Python 会认为你在创建一个新的局部变量:

python
def outer():
    count = 0
    
    def inner():
        count += 1  # 错误!UnboundLocalError
        return count
    
    return inner

counter = outer()
counter()  # UnboundLocalError: local variable 'count' referenced before assignment

4. 防止闭包副作用

在使用 Lambda 表达式时,如果使用了外部变量,那么在调用 Lambda 表达式时,外部变量的值会被保存,而不是在调用时获取。

python
def demo():
    funcs = [lambda x: x + n for n in range(5)]
    for f in funcs:
        print(f(0))

此时,输出结果为:

python
4
4
4
4
4

这是因为所有的 lambda 函数都引用了同一个变量 n,而 n 的最终值是 4。

4.1 使用默认参数解决

如果我们希望输出结果为 0 1 2 3 4,可以使用默认参数:

python
def demo():
    funcs = [lambda x, n=n: x + n for n in range(5)]
    for f in funcs:
        print(f(0))

默认参数在函数定义时就被求值,因此每个 lambda 函数都有自己的 n 值。

4.2 使用 functools.partial

也可以使用 functools.partial

python
from functools import partial

def demo():
    funcs = [partial(lambda n, x: x + n, n) for n in range(5)]
    for f in funcs:
        print(f(0))

4.3 使用生成器表达式

另一种方法是立即执行 lambda 表达式:

python
def demo():
    funcs = [(lambda n: lambda x: x + n)(n) for n in range(5)]
    for f in funcs:
        print(f(0))

5. 闭包与装饰器

装饰器本质上就是闭包的一种应用:

python
def timer_decorator(func):
    import time
    
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} took {end - start:.4f} seconds")
        return result
    
    return wrapper

@timer_decorator
def slow_function():
    import time
    time.sleep(1)
    return "Done"

slow_function()  # slow_function took 1.0001 seconds

6. 闭包的性能考虑

闭包会保持对外部变量的引用,这可能导致内存占用增加:

python
def create_large_closure():
    large_data = [i for i in range(1000000)]  # 大数据
    
    def get_first():
        return large_data[0]
    
    return get_first

# large_data 会一直存在于内存中,直到 func 被销毁
func = create_large_closure()

如果只需要部分数据,考虑只保存必要的信息:

python
def create_optimized_closure():
    large_data = [i for i in range(1000000)]
    first_element = large_data[0]  # 只保存需要的数据
    
    def get_first():
        return first_element
    
    return get_first

7. 检查闭包变量

可以使用 __closure__ 属性查看闭包中的变量:

python
def outer():
    x = 10
    y = 20
    
    def inner():
        return x + y
    
    return inner

func = outer()
print(func.__closure__)  # (<cell at 0x...: int object at 0x...>, ...)
print([cell.cell_contents for cell in func.__closure__])  # [10, 20]

8. 闭包与类的比较

闭包和类都可以用来封装状态,但各有优缺点:

python
# 使用闭包
def make_counter():
    count = 0
    
    def increment():
        nonlocal count
        count += 1
        return count
    
    return increment

# 使用类
class Counter:
    def __init__(self):
        self.count = 0
    
    def increment(self):
        self.count += 1
        return self.count

# 使用对比
counter1 = make_counter()
counter2 = Counter()

print(counter1())  # 1
print(counter2.increment())  # 1

闭包更轻量级,适合简单的状态封装;类更强大,适合复杂的对象和继承关系。