一个很Cool的Idear->Python的尾递归优化

偶然在国外一个网站瞅到的,非常的酷,发出来共享一下。一般来说,Python和Java,C#一样是没有尾递归自动优化的能力的,递归调用受到调用栈长度的限制被广泛的诟病,但是这个狂人用一个匪夷所思的方法解决了这个问题并在Python上实现了,从此Python的递归调用再也不用受到调用栈长度的制约,太酷了。

首先我们还是从递归说起,之前我发过一篇 《浅谈递归过程以及递归的优化》其中用到了斐波那契数来作为例子.线性递归的算法由于太过一低效就被我们Pass掉了,我们先来看尾递过方式的调用:

1 def Fib(n,b1=1,b2=1,c=3):
2     if n<3:
3         return 1
4     else:
5         if n==c:
6             return b1+b2
7         else:
8             return Fib(n,b1=b2,b2=b1+b2,c=c+1)
9 

 

 

这段程序我们来测试一下,调用 Fib(1001)结果:

>>> def Fib(n,b1=1,b2=1,c=3):

...     if n<3:

...         return 1

...     else:

...         if n==c:

...             return b1+b2

...         else:

...             return Fib(n,b1=b2,b2=b1+b2,c=c+1)

... 

>>> Fib(1001)

70330367711422815821835254877183549770181269836358732742604905087154537118196933579742249494562611733487750449241765991088186363265450223647106012053374121273867339111198139373125598767690091902245245323403501L

>>> 

如果我们用Fib(1002),结果,茶几了,如下:

 

  .....

  File "<stdin>", line 8, in Fib

  File "<stdin>", line 8, in Fib

  File "<stdin>", line 8, in Fib

  File "<stdin>", line 8, in Fib

  File "<stdin>", line 8, in Fib

  File "<stdin>", line 8, in Fib

RuntimeError: maximum recursion depth exceeded

>>> 

 

好了,现在我们来尾递归优化

我们给刚才的Fib函数增加一个Decorator,如下:

 1 @tail_call_optimized

2 def Fib(n,b1=1,b2=1,c=3):
3     if n<3:
4         return 1
5     else:
6         if n==c:
7             return b1+b2
8         else:
9             return Fib(n,b1=b2,b2=b1+b2,c=c+1)

 

 

恩,就是这个@tail_call_optimized的装饰器 ,这个装饰器使Python神奇的打破了调用栈的限制。

这下即使我们Fib(20000),也能在780ms跑出结果(780ms是以前博文提到那台2000元的上网本跑出来的结果)

 

不卖关子了,下面我们来看看这段神奇的代码:

 1 import sys  
 2   
 3 class TailRecurseException:  
 4   def __init__(self, args, kwargs):  
 5     self.args = args  
 6     self.kwargs = kwargs  
 7   
 8 def tail_call_optimized(g):  
 9   """  
10   This function decorates a function with tail call  
11   optimization. It does this by throwing an exception  
12   if it is it's own grandparent, and catching such  
13   exceptions to fake the tail call optimization.  
14     
15   This function fails if the decorated  
16   function recurses in a non-tail context.  
17   """  
18   def func(*args, **kwargs):  
19     f = sys._getframe()  
20     if f.f_back and f.f_back.f_back and f.f_back.f_back.f_code == f.f_code:  
21       raise TailRecurseException(args, kwargs)  
22     else:  
23       while 1:  
24         try:  
25           return g(*args, **kwargs)  
26         except TailRecurseException, e:  
27           args = e.args  
28           kwargs = e.kwargs  
29   func.__doc__ = g.__doc__  
30   return func  
31 

 

 

使用的方法前面已经展示了,令我感到大开眼界的是,作者用了抛出异常然后自己捕获的方式来打破调用栈的增长,简直是太匪夷所思了。而且效率问题,和直接尾递归Fib相比大概造成了五倍的时间开销。

最后很不可思议的,尾递归优化的目的达成了。

 

本代码的出处:http://code.activestate.com/recipes/474088/

 

 还有一个JavaScript的实现:http://w3future.com/weblog/2006/02/#tailCallEliminationInJavascript

 

如果有同学出了C#或者Java的实现,请发出来供大家瞻仰,呵呵 

 

 

posted on 2010-09-16 00:46 亚历山大同志 阅读(4076) 评论(9) 编辑 收藏

评论

#1楼 2010-09-16 00:50 谢小漫      

亚历山大是个好同志  回复 引用 查看   

#2楼 2010-09-16 07:51 磬石      

看明白了,但这真能提高效率?每次都会有个异常  回复 引用 查看   

#3楼 2010-09-16 09:11 allentranks      

这个就是kick吗  回复 引用 查看   

#4楼 2010-09-16 09:18 失落映画      

不怎么了解Python.
不过"作者用了抛出异常然后自己捕获的方式来打破调用栈的增长"这种方式造成的开销增加太惊人了,计算数字越大越明显,违背了大数计算的初衷了.

不过想法真的很天马行空.
 回复 引用 查看   

#5楼 2010-09-16 09:35 徐少侠      

不过调用栈的开销也是要考虑的
先学习ing
 回复 引用 查看   

#6楼 2010-09-16 10:04 轩辕法王      

不错,友情顶贴  回复 引用 查看   

#7楼 2010-09-16 11:37 Milo Yip      

"idear"?

雖然優化了stack,但估計會劣化了速度。
 回复 引用 查看   

#8楼 2011-02-26 13:49 Dreampuf      

...raise看着就恶心.  回复 引用 查看   

#9楼 2011-04-28 16:17 take it and go      

idear?  回复 引用 查看   

导航

公告


放一首适合飚车的音乐,听这个开车会不知不觉的加速
昵称:亚历山大同志
园龄:5年1个月
荣誉:推荐博客
粉丝:117
关注:0
<2010年9月>
2930311234
567891011
12131415161718
19202122232425
262728293012
3456789

统计

搜索

 
 

常用链接

最新随笔

我的标签

随笔分类(128)

随笔档案(134)

相册

朋友的Blog

同事的Blog

积分与排名

最新评论

阅读排行榜

评论排行榜

推荐排行榜