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使用arp欺骗伪造网关的方法
Apr 24 Python
Django中处理出错页面的方法
Jul 15 Python
Python编程之gui程序实现简单文件浏览器代码
Dec 08 Python
详解python OpenCV学习笔记之直方图均衡化
Feb 08 Python
python查询mysql,返回json的实例
Mar 26 Python
python实现倒计时小工具
Jul 29 Python
django项目中使用手机号登录的实例代码
Aug 15 Python
小 200 行 Python 代码制作一个换脸程序
May 12 Python
如何以Winsows Service方式运行JupyterLab
Aug 30 Python
想学画画?python满足你!
Dec 24 Python
浅谈Selenium+Webdriver 常用的元素定位方式
Jan 13 Python
基于PyTorch中view的用法说明
Mar 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 字符串编码截取函数(兼容utf-8和gb2312)
2009/05/02 PHP
PHP+MySQL 手工注入语句大全 推荐
2009/10/30 PHP
PHP实现发送邮件的方法(基于简单邮件发送类)
2015/12/17 PHP
详解yii2实现分库分表的方案与思路
2017/02/03 PHP
PHP小白必须要知道的php基础知识(超实用)
2017/10/10 PHP
YII框架http缓存操作示例
2019/04/29 PHP
再次更新!MSClass (Class Of Marquee Scroll通用不间断滚动JS封装类 Ver 1.6)
2007/02/05 Javascript
一些主流JS框架中DOMReady事件的实现小结
2011/02/12 Javascript
jQuery 瀑布流 浮动布局(一)(延迟AJAX加载图片)
2012/05/23 Javascript
JavaScript高级程序设计(第3版)学习笔记7 js函数(上)
2012/10/11 Javascript
用JS做的简单的可折叠的两级树形菜单
2013/09/21 Javascript
在JavaScript中重写jQuery对象的方法实例教程
2014/08/25 Javascript
JavaScript实现同步于本地时间的动态时间显示方法
2015/02/02 Javascript
JavaScript中的定时器之Item23的合理使用
2015/10/30 Javascript
JavaScript事件类型中UI事件详解
2016/01/14 Javascript
javascript实现移动端上的触屏拖拽功能
2016/03/04 Javascript
BootStrap下jQuery自动完成的样式调整
2016/05/30 Javascript
ajax分页效果(bootstrap模态框)
2017/01/23 Javascript
在vue项目中使用Nprogress.js进度条的方法
2018/01/31 Javascript
React diff算法的实现示例
2018/04/20 Javascript
node.js到底要不要加分号浅析
2018/07/11 Javascript
详解Vue+axios+Node+express实现文件上传(用户头像上传)
2018/08/10 Javascript
小程序如何写动态标签的实现方法
2020/02/05 Javascript
vue实现分页的三种效果
2020/06/23 Javascript
Python学生信息管理系统修改版
2018/03/13 Python
Python利用pandas处理Excel数据的应用详解
2019/06/18 Python
Python 获取 datax 执行结果保存到数据库的方法
2019/07/11 Python
python数据预处理之数据标准化的几种处理方式
2019/07/17 Python
python sqlite的Row对象操作示例
2019/09/11 Python
PyCharm使用Docker镜像搭建Python开发环境
2019/12/26 Python
python之语音识别speech模块
2020/09/09 Python
关键字throw与throws的用法差异
2016/11/22 面试题
英语老师推荐信
2014/02/26 职场文书
经典商业广告词
2014/03/13 职场文书
保管员岗位职责
2015/02/14 职场文书
PHP实现创建以太坊钱包转账等功能
2021/04/21 PHP