Python中使用装饰器来优化尾递归的示例


Posted in Python onJune 18, 2016

尾递归简介
尾递归是函数返回最后一个操作是递归调用,则该函数是尾递归。
递归是线性的比如factorial函数每一次调用都会创建一个新的栈(last-in-first-out)通过不断的压栈,来创建递归, 很容易导致栈的溢出。而尾递归则使用当前栈通过数据覆盖来优化递归函数。
阶乘函数factorial, 通过把计算值传递的方法完成了尾递归。但是python不支出编译器优化尾递归所以当递归多次的话还是会报错(学习用)。

eg:

def factorial(n, x):
  if n == 0:
    return x
  else:
    return factorial(n-1, n*x)

print factorial(5, 1) # 120

尾递归优化
这里用到了斐波那契数来作为例子.线性递归的算法由于太过一低效就被我们Pass掉了,我们先来看尾递过方式的调用:

(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)结果:

>>> 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,如下:

@tail_call_optimized
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)

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

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

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

class TailRecurseException: 
 def __init__(self, args, kwargs): 
 self.args = args 
 self.kwargs = kwargs 
 
def tail_call_optimized(g): 
 """ 
 This function decorates a function with tail call 
 optimization. It does this by throwing an exception 
 if it is it's own grandparent, and catching such 
 exceptions to fake the tail call optimization. 
 
 This function fails if the decorated 
 function recurses in a non-tail context. 
 """ 
 def func(*args, **kwargs): 
 f = sys._getframe() 
 if f.f_back and f.f_back.f_back and f.f_back.f_back.f_code == f.f_code: 
  raise TailRecurseException(args, kwargs) 
 else: 
  while 1: 
  try: 
   return g(*args, **kwargs) 
  except TailRecurseException, e: 
   args = e.args 
   kwargs = e.kwargs 
 func.__doc__ = g.__doc__ 
 return func

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

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

Python 相关文章推荐
python多线程抓取天涯帖子内容示例
Apr 03 Python
python实现挑选出来100以内的质数
Mar 24 Python
在Python中处理字符串之ljust()方法的使用简介
May 19 Python
简单掌握Python的Collections模块中counter结构的用法
Jul 07 Python
python万年历实现代码 含运行结果
May 20 Python
解决python文件字符串转列表时遇到空行的问题
Jul 09 Python
PyQt 线程类 QThread使用详解
Jul 16 Python
Flask框架通过Flask_login实现用户登录功能示例
Jul 17 Python
Python3+Pycharm+PyQt5环境搭建步骤图文详解
May 29 Python
Django框架ORM数据库操作实例详解
Nov 07 Python
python列表返回重复数据的下标
Feb 10 Python
python的sys.path模块路径添加方式
Mar 09 Python
举例讲解Python面向对象编程中类的继承
Jun 17 #Python
浅谈python为什么不需要三目运算符和switch
Jun 17 #Python
python运行时间的几种方法
Jun 17 #Python
从局部变量和全局变量开始全面解析Python中变量的作用域
Jun 16 #Python
实例讲解Python中global语句下全局变量的值的修改
Jun 16 #Python
最大K个数问题的Python版解法总结
Jun 16 #Python
Python中的多行注释文档编写风格汇总
Jun 16 #Python
You might like
php print EOF实现方法
2009/05/21 PHP
PHP中ltrim与rtrim去除左右空格及特殊字符实例
2016/01/07 PHP
javascript 防止刷新,后退,关闭
2010/08/07 Javascript
读jQuery之五(取DOM元素)
2011/06/20 Javascript
JavaScript操作XML 使用百度RSS作为新闻源示例
2012/02/17 Javascript
jQuery创建平滑的页面滚动(顶部或底部)
2013/02/26 Javascript
Firefox和IE兼容性问题及解决方法总结
2013/10/08 Javascript
jQuery中width()方法用法实例
2014/12/24 Javascript
JavaScript实现按照指定长度为数字前面补零输出的方法
2015/03/19 Javascript
jQuery实现类似淘宝网图片放大效果的方法
2015/07/08 Javascript
全面解析Bootstrap中nav、collapse的使用方法
2016/05/22 Javascript
一个基于react的图片裁剪组件示例
2018/04/18 Javascript
小程序分享模块超级详解(推荐)
2019/04/10 Javascript
微信小程序 SOTER 生物认证DEMO 指纹识别功能
2019/12/13 Javascript
js节流防抖应用场景,以及在vue中节流防抖的具体实现操作
2020/09/21 Javascript
Node使用koa2实现一个简单JWT鉴权的方法
2021/01/26 Javascript
[04:10]DOTA2英雄梦之声_第11期_圣堂刺客
2014/06/21 DOTA
[09:33]2015国际邀请赛第四日TOP10
2015/08/08 DOTA
python类参数self使用示例
2014/02/17 Python
python实现随机梯度下降(SGD)
2020/03/24 Python
对Python 语音识别框架详解
2018/12/24 Python
python 实现UTC时间加减的方法
2018/12/31 Python
python 实现简单的FTP程序
2019/12/27 Python
使用Python求解带约束的最优化问题详解
2020/02/11 Python
Python configparser模块配置文件过程解析
2020/03/03 Python
Python读取文件内容为字符串的方法(多种方法详解)
2020/03/04 Python
Python任务自动化工具tox使用教程
2020/03/17 Python
django实现模板中的字符串文字和自动转义
2020/03/31 Python
html5 svg 中元素点击事件添加方法
2013/01/16 HTML / CSS
联想瑞士官方网站:Lenovo Switzerland
2017/11/19 全球购物
上课迟到检讨书
2014/01/19 职场文书
钳工实训报告总结
2014/11/04 职场文书
辩论赛开场白大全(主持人+辩手)
2015/05/29 职场文书
安全主题班会教案
2015/08/12 职场文书
英语导游欢迎词
2015/09/30 职场文书
党员学习中国梦心得体会
2016/01/05 职场文书