谈谈闭包
2015-09-09 14:48
260 查看
我第一次听说“闭包”这个概念是在学习Lua的时候。由于此前并没有接触到函数式编程的语言,所以满脑子C/C++的思维方式的我被“闭包”困惑了很久。我找到了一个比较通俗的闭包定义:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)。Lua的学习资料上一般都会有一个这样关于闭包的例子:
首先,new_counter()是一个返回函数的函数;然后这个被返回的counter函数会更改其外部函数(new_counter)的局部变量,并返回计数值。注意counter()函数是在new_counter()函数执行完毕之后被调用的(这好像是废话!),而counter()函数的作用就是读写变量n,所以要理解这个例子的关键在于这个变量n。
熟悉C/C++运行机制的同学都应该知道,C/C++的局部变量是存在于堆栈上的。一个函数执行完毕后,栈顶指针要退回,所以局部变量就消失了。然而支持闭包的语言Lua,Python等等的局部变量的存放方式却不一样,这基本上也是一句废话(这些动态语言都不直接操作硬件堆栈),但是记住这个结论就好——局部变量也是可以长期有效的(在函数返回之后)。而内部函数——闭包(可返回到更外层的作用域)就可以在随后的时间里执行,即操作外层函数(如new_counter函数)的局部变量。就这样,数据成为了函数的附庸。
然后说说Python的闭包吧!其实写这个东西的念头就是来自于看《Python源码剖析》时遇到的一些问题。还是先看看这个Python版的计数器吧!
非常不好意思,这例子是不能运行的。Python会报错:“local variable ‘n’ referenced before assignment”。这是因为Python默认变量为局部变量,所以在第四行,它认为n是一个局部变量,并且是一个没有被初始化的局部变量,这自然是要报错了。Python规定变量的作用域要遵守“LEGB”的规则,即按照Local作用域——直接外围作用域——全局作用域——builtin作用域的顺序去寻找变量的定义。然而事实上,Python倒在了L到E的路上。在非全局变量的情况下,Python2的定义和赋值是一起的,没有什么关键字和语法说明n是直接外部作用域的变量,所以这段程序是无法运行的。有一种用list实现闭包赋值的方法,如下:
这种方法的关键就在于第四行的“n[0]”只能是赋值,这中语法使得定义和赋值能够区分开来。而且Python3中增加了nonlocal关键字,这个关键字明确的指示编译器到外部寻找变量,这才是真正的遵守了LEGB规则。
仔细一想,好像支持闭包的语言好像都是动态语言,当然Go是个例外。我猜想Go语言中,函数的局部变量应该是在堆空间中的,相当于都是new出来的。支持闭包的另一个关键点是垃圾收集,否者局部变量的管理会成为一个问题。还有就是要支持函数嵌套定义(又是废话了)。
function new_counter() local n = 0 local function counter() n = n + 1 return n end return counter end c1 = new_counter() c2 = new_counter() print(c1()) //打印1 print(c2()) //打印2
首先,new_counter()是一个返回函数的函数;然后这个被返回的counter函数会更改其外部函数(new_counter)的局部变量,并返回计数值。注意counter()函数是在new_counter()函数执行完毕之后被调用的(这好像是废话!),而counter()函数的作用就是读写变量n,所以要理解这个例子的关键在于这个变量n。
熟悉C/C++运行机制的同学都应该知道,C/C++的局部变量是存在于堆栈上的。一个函数执行完毕后,栈顶指针要退回,所以局部变量就消失了。然而支持闭包的语言Lua,Python等等的局部变量的存放方式却不一样,这基本上也是一句废话(这些动态语言都不直接操作硬件堆栈),但是记住这个结论就好——局部变量也是可以长期有效的(在函数返回之后)。而内部函数——闭包(可返回到更外层的作用域)就可以在随后的时间里执行,即操作外层函数(如new_counter函数)的局部变量。就这样,数据成为了函数的附庸。
然后说说Python的闭包吧!其实写这个东西的念头就是来自于看《Python源码剖析》时遇到的一些问题。还是先看看这个Python版的计数器吧!
def new_counter(): n = 0 def counter(): n = n + 1 return n return counter
非常不好意思,这例子是不能运行的。Python会报错:“local variable ‘n’ referenced before assignment”。这是因为Python默认变量为局部变量,所以在第四行,它认为n是一个局部变量,并且是一个没有被初始化的局部变量,这自然是要报错了。Python规定变量的作用域要遵守“LEGB”的规则,即按照Local作用域——直接外围作用域——全局作用域——builtin作用域的顺序去寻找变量的定义。然而事实上,Python倒在了L到E的路上。在非全局变量的情况下,Python2的定义和赋值是一起的,没有什么关键字和语法说明n是直接外部作用域的变量,所以这段程序是无法运行的。有一种用list实现闭包赋值的方法,如下:
def new_counter(): n = [0] def counter(): n[0] = n[0] + 1 return n[0] return counter
这种方法的关键就在于第四行的“n[0]”只能是赋值,这中语法使得定义和赋值能够区分开来。而且Python3中增加了nonlocal关键字,这个关键字明确的指示编译器到外部寻找变量,这才是真正的遵守了LEGB规则。
仔细一想,好像支持闭包的语言好像都是动态语言,当然Go是个例外。我猜想Go语言中,函数的局部变量应该是在堆空间中的,相当于都是new出来的。支持闭包的另一个关键点是垃圾收集,否者局部变量的管理会成为一个问题。还有就是要支持函数嵌套定义(又是废话了)。
相关文章推荐
- Android免Root无侵入AOP框架Dexposed
- 缓冲区溢出分析第03课:缓冲区溢出的利用
- 【性能分析】使用Intel VTune Amplifier
- C++类
- linux用date更改时间还原
- 一致性 Hash 算法
- Java mongodb 基本操作入门
- MySQL协议简单分析
- sort 升序还是降序?priority_queue 大根堆还是小根堆?
- 实现基于异或的双链表
- 详解CloudFoundry中各个组件的作用
- 交叉编译python 2.7.3 for arm
- 比较两种mysql递归tree查询效率-mysql递归tree
- HDU 2294【DP+快速幂】
- 计蒜客 第16题:爬楼梯
- synchronized
- JavaScript并非“按值传递”
- 如何使用iClap进行产品管理?
- Struts2的属性驱动与模型驱动的区别
- SparkSQLTest.scala