dfdh 发表于 2015-12-15 13:14:12

Python闭包(closures)的理解及分析

闭包(closures)这一概念在许多脚本语言,比如javascript,python,lua中普遍存在,对于长期用c/c++编程的程序员来讲,闭包的概念不是特别容易理解,也不清楚闭包的应用场景。确实,闭包的概念是面向过程编程的产物,其本质含义是一个函数和某个数据绑定在一起,数据的生命周期和函数的生命周期一样长。从上面这段话中,隐隐约约感觉到了什么?下面我们来看一下比较正式的闭包定义,并看一个简单的闭包例子。
python闭包定义:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure).

1 #!/usr/bin/env python
2
3 def fun(m):
4   def add(n):
5         return m + n
6   return add
7
8 f1 = fun(10)
9 print f1(1) #输出11
10 print f1(2) #输出12
11 print f1(3) #输出13
12
13 f2 = fun(20)
14 print f2(1) #输出21
15 print f2(2) #输出22
16 print f2(3) #输出23

闭包是定义在一个函数内部的函数,该函数引用了其外层函数的局部变量或型参变量,然后外层函数将闭包函数做为返回值返回。外面的调用方可以直接调用闭包体(比如上述代码中的f1),外层函数的局部变量(比如fun中的局部变量m)都不会消失,直到闭包体消亡。另外一个特点是定义不同的闭包体的时候,比如f1和f2,两者之间的局部变量是互相不干预的,是各自独立的一份。
根据上述例子,我们很容易将闭包跟C++的类、对象、成员函数、成员变量一一对应起来,闭包和面向对象的类本质上属于同一个思想的东西。为了说明这一点,我们举一个更加复杂的例子:
1 #!/usr/bin/env python                                                   
2 def fun(m):                                                               
3   n =                                                                
4   def inc():                                                            
5         k = m + n                                                   
6         n += 1                                                         
7         m += 1                                                         
8         print k                                                         
9         
10   def sub():                                                            
11         n -= 1                                                         
12         m -= 1                                                         
13   
14   return inc, sub                                                      
15         
16 f_inc, f_sub = fun()                                                   
17         
18 times = 0
19 while True:                                                               
20   f_inc()                                                               
21   times += 1
22   if times == 10: break                                                
23         
24 times = 0
25 while True:                                                               
26   f_sub()                                                               
27   times += 1                                                            
28   if times == 10: break                                                
29   
30 times = 0                                                               
31 while True:                                                               
32   f_inc()                                                               
33   times += 1                                                            
34   if times == 10: break         

fun函数返回两个闭包函数f_inc及f_sub,这两个闭包函数引用了fun里面的局部变量m及n。从面向对象的观点来看,可以将fun理解为一个类,f_inc及f_sub理解为类的成员函数,fun里面的局部变量m,n可以理解为类的成员变量。对于函数fun的调用,比如fun(),可以理解为类的实例化。从这些观点来理解闭包,就非常容易理解闭包的各种特性了。为了方便大家理解,大家可以参考下面这段c++代码,感觉闭包和C++的类、对象概念是不是完全一致?
1 #include
2
3 class CFun
4 {
5 public:
6   CFun(int m)
7   {
8         _n = 1;
9         _m = m;
10   }
11
12 public:
13   void f_inc()
14   {
15         int k = _m + _n;
16         _n++;
17         _m++;
18         printf("%d\r\n", k);
19   }
20
21   void f_sub()
22   {
23         _n--;
24         _m--;
25   }
26
27 private:
28   int _n;
29   int _m;
30
31 };
33 int main()
34 {
35   CFun fun(2);
36   int times = 0;
37   while(times < 10)
38   {
39         fun.f_inc();
40         times++;
41   }
42
43   times = 0;
44   while(times < 10)
45   {
46         fun.f_sub();
47         times++;
48   }
49
50   times = 0;
51   while(times < 10)
52   {
53         fun.f_inc();
54         times++;
55   }
56
57   return 0;
58 }

根据闭包的特性,可以看出其比较适合用于回调函数。因为该回调函数天然和一堆数据绑定在一起,可以省去大量的数据存储、读取工作。试想一下再面向过程编程中,若没有闭包,在设计回调函数中必然有一块数据需要单独存储,在回调时再将这块数据找出来,然后塞给这个回调函数。有了闭包后再这块可以简单很多。但在面向对象编程中,闭包基本很难发挥什么特点,对于回调我们可以设计虚函数等方式来实现数据和函数的绑定。
下面再来简单分析一下闭包的实现,整个闭包的实现和脚本语言的内存回收机制是密不可分的。对于上面的例子,可以简单理解为当 f_inc, f_sub = fun() 执行时,fun里面的局部变量都被开辟,且引用计数都变为了3(f_inc,f_sub,fun都取得了一个),当执行完时,虽然fun消亡,引用计数减一,但局部变量的引用计数依旧为2,不会被系统收回。当f_inc和f_sub被定义时,其作用域链已经被确定,如下图所示。当fun()被执行时,相当于确定了一个执行环境,作用域链上所有的变量都会被初始化。整个概念跟类的定义及类的实例化一模一样,因此采用面向对象的观念来理解闭包,就非常容易想清楚其背后的实现原理及应用场景。
http://files.iyunv.net/upload/201007/20100703001017585.jpg


参考资料:
http://www.iyunv.net/article/24101.htm
http://blog.csdn.net/marty_fu/article/details/7679297












页: [1]
查看完整版本: Python闭包(closures)的理解及分析