闭包是什么

闭包是什么?《Functional programming using standard ML》是这么定义的:一个包含了自由变量的开放表达式,它和该自由变量的约束环境组合在一起后,实现了一种封闭的状态。第一遍看时完全不知道它在说啥。自由变量?开放表达式?约束环境?但在理解了之后,发现闭包确实是这么回事。但在理解闭包之前,我们需要知道一些基础性的概念。让我们先从命名说起。

写程序时,我们对变量和函数命名,使代码更具有可读性和重用性。当需要之前的变量和函数时,我们只需使用它的名字即可。那么运行时程序是怎么知道名字对应的值或者函数呢?


实际上,在程序运行时,它会创建一个名字和内容的对照表,类似于 Map。它以变量或函数的名称为键,其内容为值。
以上面这段代码为例,运行时它会创建一个全局对照表,{"number": 0, "print_number": print_number 函数}。当调用 print(number) 时,它首先会查找到 number 对应的值是 0,然后将其打印出来。实际上,一个程序不只有一个对照表,它在每调用一个函数时都会创建新的对照表。在调用 print_number 时,它会创建一个对照表,{"number": 1}。注意,这里的 number 不同于全局对照表中的 number,修改它的值并不会影响在全照对照表中的 number,可从最后一次调用 print(number) 得到 0 来验证。在 print_number 的调用结束后,它所创建的对照表也会被销毁。

了解了对照表之后,我们再说说闭包是什么。


上面是一段简单的 Python 代码,实现了每调用一次显示的数字加 1 的函数 make_counter。函数 make_counter 中定义了变量 count 和 函数 push。通过调用函数 make_counter 将返回的 push 函数赋给变量 c,然后调用它 3 次。每调用一次,显示的值就加 1。注意,每次调用 c 时结果都是不一样的。
调用函数 make_counter 时创建了对照表 f1,{"count": 0, "push": push 函数},并将返回的 push 函数赋值给 c。由于此时全局对照表中 c 指向 push 函数,而 push 函数又依赖于 f1,所以 f1 没有在 make_counter 调用结束后被销毁。 随后 c 每次被调用时,函数 push 会创建新的对照表,此对照表依赖于它被定义时的对照表,也就是 f1。它会从 f1 中查找到变量 count 的值,加 1 然后返回。
函数 push 使用了变量 count,但变量 count 并不是在函数 push 中定义的。这种变量被称为自由变量。函数 push 就是一个包含了自由变量的开放函数,也就是闭包。其实可以简单地把闭包想象成带有某种状态的函数。

最后强烈推荐下 Python Tutor,可以直观地了解程序是怎么执行的。文中的代码执行过程都是由这个网站提供的。

参考