基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

Python 中的闭包(Closure)

知识点图片

在 Python 中,闭包(Closure) 是一个非常重要且强大的概念。简单来说,闭包让一个函数能够“记住”并访问其定义时所在的外部作用域中的变量,即使那个外部作用域的函数已经执行结束了。

以下是关于 Python 闭包的详细解析:


1. 什么是闭包?

要形成一个闭包,必须同时满足以下 三个条件

  1. 嵌套函数:必须有一个嵌套在其他函数内部的函数(即:外部函数里面定义了内部函数)。
  2. 引用外部变量:内部函数必须引用外部函数中定义的变量(非全局变量)。
  3. 返回内部函数:外部函数必须返回内部函数的引用。

一句话总结:闭包 = 内部函数 + 定义时的环境(外部变量)。


2. 基本示例

让我们看一个最经典的例子:

python
def make_multiplier(n):
    # 外部函数
    
    def multiplier(x):
        # 内部函数
        # 这里引用了外部函数的变量 n
        return x * n
    
    # 返回内部函数
    return multiplier

# 1. 创建闭包
times3 = make_multiplier(3)  # n = 3
times5 = make_multiplier(5)  # n = 5

# 2. 调用闭包
print(times3(10))  # 输出: 30 (10 * 3)
print(times5(10))  # 输出: 50 (10 * 5)

发生了什么?

  • 当我们调用 make_multiplier(3) 时,外部函数执行完毕并返回了 multiplier 函数。
  • 通常情况下,函数执行完后,其局部变量(如 n)会被销毁。
  • 但在闭包中,times3 这个函数对象“携带”了 n=3 这个环境信息。即使 make_multiplier 早就结束了,times3 依然记得 n 是 3。

3. 查看闭包的内部 (__closure__)

Python 将闭包引用的外部变量存储在函数对象的 __closure__ 属性中。

python
print(times3.__closure__)
# 输出: (<cell at 0x...: int object at 0x...>,)

print(times3.__closure__[0].cell_contents)
# 输出: 3

这证明了变量 n 被保存在了一个特殊的“单元(cell)”中,跟随函数对象一起存在。


4. 修改闭包变量 (nonlocal 关键字)

默认情况下,内部函数只能读取外部变量。如果你尝试在内部函数中修改外部变量(例如重新赋值),Python 会认为你是在定义一个新的局部变量,从而报错或产生意外结果。

在 Python 3 中,使用 nonlocal 关键字可以解决这个问题。

示例:计数器

python
def make_counter():
    count = 0  # 外部变量
    
    def inner():
        nonlocal count  # 声明我们要修改外部的 count
        count += 1
        return count
    
    return inner

counter = make_counter()
print(counter())  # 输出: 1
print(counter())  # 输出: 2
print(counter())  # 输出: 3

如果没有 nonlocal countcount += 1 会抛出 UnboundLocalError


5. 闭包的实际应用场景

你可能在想:“我为什么要用闭包?直接用类(Class)不行吗?”
闭包通常用于以下场景,比类更轻量、更优雅:

A. 数据隐藏与封装

闭包可以避免使用全局变量,将数据隐藏在函数内部,只有内部函数能访问。

B. 装饰器 (Decorators)

这是 Python 中闭包最广泛的应用。装饰器本质上就是一个闭包:它接受一个函数,返回一个包装后的新函数。

python
def my_decorator(func):
    def wrapper():
        print("在函数执行前做点什么...")
        func()
        print("在函数执行后做点什么...")
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")

say_hello()

C. 延迟计算 (Lazy Evaluation)

你可以预先配置函数的某些参数,等到真正需要计算时再调用(类似于偏函数 functools.partial)。


6. 常见的“坑”:循环中的闭包

这是一个面试中经常出现的问题:延迟绑定(Late Binding)

错误示例:

python
functions = []
for i in range(3):
    def inner():
        return i  # 引用了外部变量 i
    functions.append(inner)

# 你期望输出 0, 1, 2
# 但实际输出是:
print(functions[0]())  # 2
print(functions[1]())  # 2
print(functions[2]())  # 2

原因:闭包引用的是变量 i内存地址,而不是 i 在定义时的。当循环结束时,i 的值变成了 2。所有的闭包都指向同一个 i,所以它们都返回 2。

解决方案:使用默认参数将当前的值绑定到局部变量。

python
functions = []
for i in range(3):
    def inner(x=i):  # x 在定义时就被赋值为当前的 i
        return x
    functions.append(inner)

print(functions[0]())  # 0
print(functions[1]())  # 1
print(functions[2]())  # 2

总结

  • 定义:闭包是引用了自由变量的函数。
  • 核心:即使外部作用域失效,闭包依然能记住外部变量。
  • 修改:修改外部变量需要使用 nonlocal
  • 用途:装饰器、数据隐藏、函数工厂。
00:00
00:00