Logo
Section 1 / 8

什么是闭包?

准备好了解闭包,并学习使用闭包的必要性及优势。

导入

什么是闭包?

准备好了解闭包,并学习使用闭包的必要性及优势。

在函数式编程中,我们有时会编写创建其他函数的函数。一种简单而优雅的方式是使用闭包(closure)。闭包提供了一种动态创建函数的方法,类似于匿名函数(lambda),但功能更强大。

内部函数

让我们先来看一下内部函数的概念。内部函数是指定义在另一个函数内部的函数。下面是一个例子:

1def print3(): 2 def print_hello(): 3 print('hello') 4 print_hello() 5 print_hello() 6 print_hello() 7 8# 主程序 9print3() 10print_hello() # 这将引发错误

print_hello 是 print3 的内部函数。具体来说,print3 首先定义了 print_hello,然后调用它三次。

运行主程序的结果是:

  • 调用 print3 会打印三次 "hello"。
  • 调用 print_hello 会出错,因为它只在 print3 内部可见。

返回一个内部函数

函数可以返回一个内部函数,如下所示:

1def make_print(): 2 def print_hello(): 3 print('hello') 4 return print_hello 5 6# 主程序 7fn = make_print() 8fn()

在这个例子中,make_print 函数定义了一个内部函数 print_hello。但它并没有调用 print_hello,而是直接将其作为函数对象返回。

当我们在主程序中调用 make_print 时,会将函数对象赋值给变量 fn。这意味着 fn 实际上是内部函数 print_hello 的别名。因此,当我们执行 fn() 时,会如预期一样输出 "hello"。

这虽然有趣,但目前并不特别有用。我们当然可以直接把 print_hello 定义为顶层函数,而无需写这么多代码。

闭包的诞生

接下来才是关键所在,我们真正创建了第一个闭包:

1def make_printx(x): 2 def printx(): 3 print(x) 4 return printx 5 6# 主程序 7fn1 = make_printx(7) 8fn2 = make_printx(100) 9fn1() 10fn2()

在上面的例子中,外层函数 make_printx 接收一个参数,而内部函数 printx 使用了这个参数。这当然没问题,因为内部函数可以访问其外部函数的局部变量和参数。

现在我们调用 make_printx,传入值 7。这会创建一个 printx 函数对象,但重要的是:这个函数对象与 x = 7 的值相关联。函数对象与 x 值的组合称为一个闭包。

在上述代码中,fn1 是 printx 的闭包,其中 x 的值为 7;fn2 是 printx 的闭包,其中 x 的值为 100。每次调用 fn1,都会输出 7;每次调用 fn2,都会输出 100。

更实用的闭包示例

假设我们需要一个函数,用于打印某个值,并自动用括号将其包围起来。同时,我们希望能够自定义括号类型,比如 [x]、{x} 或 <<x>>。

我们可以用闭包来实现这一点:

1def make_printb(start, end): 2 def printb(s): 3 print(start + s + end) 4 return printb 5 6# 主程序 7sq = make_printb('[', ']') 8dbl_ang = make_printb('<<', '>>') 9sq('hello') 10dbl_ang('world')

这里,make_printb 接收两个参数:起始括号和结束括号。不过,在这个例子中,内部函数 printb 接收一个参数 s,表示要打印的实际字符串内容。这意味着括号类型在创建闭包时就固定了,但可以在调用闭包时指定具体内容。

因此,当我们创建闭包 sq 时,括号被设置为方括号。每次调用 sq,都会使用方括号,但中间的内容可以任意更改。同样地,dbl_ang 始终使用双尖括号。

这就是如何通过一个简单的闭包,创建一个“工厂”,生成一系列能以不同括号样式打印文本的函数。

什么是闭包?

那么,到底什么是闭包?一个闭包通常需要满足三个条件:

  • 外层函数中包含一个内部函数。
  • 外层函数具有参数和/或局部变量。
  • 外层函数将内部函数作为函数对象返回。

严格来说,任何返回内部函数的函数都是闭包,即使它没有参数(例如本课开头的 make_print 函数)。然而,如果无法改变闭包的行为,这样的闭包就没什么实际用途。

函数作为对象

创建匿名函数