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 07 Python
基于python的Tkinter编写登陆注册界面
Jun 30 Python
python 矩阵增加一行或一列的实例
Apr 04 Python
Python实现简单http服务器
Apr 12 Python
python遍历小写英文字母的方法
Jan 02 Python
opencv python统计及绘制直方图的方法
Jan 21 Python
Python生成器的使用方法和示例代码
Mar 04 Python
深入浅析Python 中 is 语法带来的误解
May 07 Python
浅谈Python2之汉字编码为unicode的问题(即类似\xc3\xa4)
Aug 12 Python
Python如何通过百度翻译API实现翻译功能
Apr 02 Python
Python实现转换图片背景颜色代码
Apr 30 Python
如何在windows下安装Pycham2020软件(方法步骤详解)
May 03 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快速统计某个数据库中每张表的数据量
2012/09/04 PHP
PHP实现多文件上传的方法
2015/07/08 PHP
PHP面向对象程序设计重载(overloading)操作详解
2019/06/13 PHP
使用jquery给input和textarea设定ie中的focus
2008/05/29 Javascript
精心挑选的15个jQuery下拉菜单制作教程
2012/06/15 Javascript
jQuery插件开发全解析
2012/10/10 Javascript
浏览器窗口加载和大小改变事件示例
2014/02/27 Javascript
extjs 时间范围选择自动判断的实现代码
2014/06/24 Javascript
使用Raygun对Node.js应用进行错误处理的方法
2015/06/23 Javascript
JS实现Select的option上下移动的方法
2016/03/01 Javascript
js判断出两个字符串最大子串的函数实现方法
2016/11/01 Javascript
详解vue mixins和extends的巧妙用法
2017/12/20 Javascript
详解.vue文件解析的实现
2018/06/11 Javascript
clipboard在vue中的使用的方法示例
2018/10/19 Javascript
VUE安装使用教程详解
2019/06/03 Javascript
深入解析koa之中间件流程控制
2019/06/17 Javascript
Vue打包后访问静态资源路径问题
2019/11/08 Javascript
js实现图片实时时钟
2020/01/15 Javascript
[01:03:18]DOTA2-DPC中国联赛 正赛 RNG vs Dynasty BO3 第一场 1月29日
2021/03/11 DOTA
pycharm 使用心得(八)如何调用另一文件中的函数
2014/06/06 Python
Python爬取Coursera课程资源的详细过程
2014/11/04 Python
python爬虫的工作原理
2017/03/05 Python
关于Python中浮点数精度处理的技巧总结
2017/08/10 Python
Python基于property实现类的特性操作示例
2018/06/15 Python
nohup后台启动Python脚本,log不刷新的解决方法
2019/01/14 Python
Python比较配置文件的方法实例详解
2019/06/06 Python
Django使用Jinja2模板引擎的示例代码
2019/08/09 Python
Python logging模块异步线程写日志实现过程解析
2020/06/30 Python
python上selenium的弹框操作实现
2020/07/13 Python
印度在线内衣和时尚目的地:Zivame
2017/09/28 全球购物
定义一结构体数组表示分数,并求两个分数相加之和
2013/06/11 面试题
软件工程专业推荐信
2013/10/28 职场文书
微博营销计划书
2014/01/10 职场文书
社区消防工作实施方案
2014/03/21 职场文书
美容院经理岗位职责
2014/04/03 职场文书
PyMongo 查询数据的实现
2021/06/28 Python