在 Python 中,闭包(Closure)是指一个函数对象能够记住并访问其定义时的外部作用域中的变量,即使在其外部作用域已经不存在时依然可以访问。具体来说:
嵌套函数:闭包通常涉及到了嵌套函数结构,即一个函数内部定义了另一个函数。
自由变量捕获:当内层函数引用了外层函数的变量(这些变量不是内层函数自己的局部变量,也不是全局变量,称为自由变量),并且外层函数返回了这个内层函数时,Python 会确保内层函数仍然能访问到那些在外层函数作用域内的自由变量,即使外层函数已经执行完毕。
状态持久化:这种特性使得闭包能够 “记住” 上次调用时的状态,因为每次调用内层函数时,它所使用的自由变量的值会被持久保持下来。
下面是一个简单的 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
时,它仍能访问并使用之前保存的基础配料值。这就是闭包的基本概念——函数能够捕获并保持其定义环境的状态。
讲得再深入一点
当然,让我们深入一点探讨闭包的几个关键特性:
词法作用域:
闭包的核心依赖于词法作用域(或静态作用域),这意味着函数能够访问它自身定义时所在的作用域中的变量,即使是在该作用域之外被调用时也是如此。在上面的三明治制作例子中,内部函数add_extra
可以访问外部函数sandwich_maker
内的base_ingredient
变量。持久化状态:
当外部函数执行完毕并返回内部函数时,这个内部函数(即闭包)就维持了对外部函数作用域内变量的引用。因此,尽管sandwich_maker
已经完成并退出,其内部创建的每个闭包(ham_sandwich_maker
等)都持有着各自的base_ingredient
值,这被称为 “闭包捕获”。延迟计算与封装性:
因为闭包保存了对自由变量的引用而非它们的值,它可以实现数据的隐藏和延迟计算。在实际应用中,你可以在不暴露底层实现细节的情况下提供一个接口(如加料制作三明治的方法)。每次调用闭包时,都会根据当前保存的状态生成一个新的结果。多态行为:
每次调用外部函数(如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
变量,所以即使多次调用同一闭包或者创建不同起始值的闭包,各个闭包所持有的状态都是独立且持久化的。