在 Python 中,闭包(Closure)是指一个函数对象能够记住并访问其定义时的外部作用域中的变量,即使在其外部作用域已经不存在时依然可以访问。具体来说:

  1. 嵌套函数:闭包通常涉及到了嵌套函数结构,即一个函数内部定义了另一个函数。

  2. 自由变量捕获:当内层函数引用了外层函数的变量(这些变量不是内层函数自己的局部变量,也不是全局变量,称为自由变量),并且外层函数返回了这个内层函数时,Python 会确保内层函数仍然能访问到那些在外层函数作用域内的自由变量,即使外层函数已经执行完毕。

  3. 状态持久化:这种特性使得闭包能够 “记住” 上次调用时的状态,因为每次调用内层函数时,它所使用的自由变量的值会被持久保持下来。

下面是一个简单的 Python 闭包示例:

def outer_function(a):
    def inner_function(b):
        return a + b  # 内部函数引用了外部函数的参数a
    return inner_function

closure_example = outer_function(10)  # 外部函数返回内部函数,并且a被绑定为10
result = closure_example(5)  # 调用返回的内部函数,使用了保存的a(即10)
print(result)  # 输出15,因为inner_function仍然记得outer_function传入的a是10

在这个例子中,inner_function就是一个闭包,因为它维持了对outer_function作用域中变量a的引用,即使在outer_function执行结束之后。当我们通过closure_example调用内层函数时,它仍然能够访问并使用那个被捕获的a的值。

还是没懂

我完全理解,闭包这个概念有时候确实需要更直观的例子来帮助理解。让我们再尝试一个生活中的类比,并且简化一下 Python 代码示例:

想象一下你有一个可以定制做三明治的快餐店。这家店有个特殊的规则:顾客可以指定一种基础配料(比如火腿),然后他们得到一个 “制作更多三明治” 的便签,上面写着如何添加额外材料(比如奶酪)到已有的基础配料上。

在编程语言中,这个场景可以这样模拟:

def sandwich_maker(base_ingredient):
    # 外部函数,相当于快餐店的定制流程
    def add_extra(added_ingredient):
        # 内部函数,相当于“制作更多三明治”的便签
        sandwich = base_ingredient + added_ingredient  # 使用保存的基础配料和新增的配料制作三明治
        return sandwich

    # 返回内部函数,就像给了顾客一个制作三明治的便签
    return add_extra

# 客户A选择火腿作为基础配料
ham_sandwich_maker = sandwich_maker("火腿")

# 后来,客户A用他们的“便签”制作了一个加了奶酪的三明治
custom_ham_sandwich = ham_sandwich_maker("奶酪")
print(custom_ham_sandwich)  # 输出:"火腿奶酪"

# 尽管原始的sandwich_maker函数已经执行完毕(相当于快餐店完成了基础配料的选择),但返回的add_extra函数(便签)仍然“记住”了基础配料是火腿

在这个例子中,“add_extra” 函数就是闭包,它维持了对外部函数(sandwich_maker)作用域中变量base_ingredient的引用。即使sandwich_maker已经执行结束并返回了,每次调用add_extra时,它仍能访问并使用之前保存的基础配料值。这就是闭包的基本概念——函数能够捕获并保持其定义环境的状态。

讲得再深入一点

当然,让我们深入一点探讨闭包的几个关键特性:

  1. 词法作用域
    闭包的核心依赖于词法作用域(或静态作用域),这意味着函数能够访问它自身定义时所在的作用域中的变量,即使是在该作用域之外被调用时也是如此。在上面的三明治制作例子中,内部函数add_extra可以访问外部函数sandwich_maker内的base_ingredient变量。

  2. 持久化状态
    当外部函数执行完毕并返回内部函数时,这个内部函数(即闭包)就维持了对外部函数作用域内变量的引用。因此,尽管sandwich_maker已经完成并退出,其内部创建的每个闭包(ham_sandwich_maker等)都持有着各自的base_ingredient值,这被称为 “闭包捕获”。

  3. 延迟计算与封装性
    因为闭包保存了对自由变量的引用而非它们的值,它可以实现数据的隐藏和延迟计算。在实际应用中,你可以在不暴露底层实现细节的情况下提供一个接口(如加料制作三明治的方法)。每次调用闭包时,都会根据当前保存的状态生成一个新的结果。

  4. 多态行为
    每次调用外部函数(如sandwich_maker)时都会创建一个新的独立闭包实例,这些实例各自维护不同的基础配料状态。例如,我们可以分别创建火腿、鸡肉等多种基础配料对应的三明治制作方法,并且它们互不影响。

下面是一个更抽象的例子来进一步展示闭包的行为:

def create_counter(start):
    count = start

    def increment():
        nonlocal count
        count += 1
        return count

    return increment

# 创建两个计数器
counter1 = create_counter(1)
counter2 = create_counter(10)

# 调用闭包进行计数
print(counter1())  # 输出:2
print(counter1())  # 输出:3
print(counter2())  # 输出:11
print(counter2())  # 输出:12

# 可以看到,counter1和counter2各自保持了自己的计数状态

在这个例子中,create_counter函数返回了一个内部函数increment,这就是一个闭包。每个闭包都保留了自己独立的count变量,所以即使多次调用同一闭包或者创建不同起始值的闭包,各个闭包所持有的状态都是独立且持久化的。


↙↙↙阅读原文可查看相关链接,并与作者交流