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笔记(2)
Oct 24 Python
Python获取当前路径实现代码
May 08 Python
利用Python破解斗地主残局详解
Jun 30 Python
基于python实现在excel中读取与生成随机数写入excel中
Jan 04 Python
DataFrame中去除指定列为空的行方法
Apr 08 Python
Python 调用PIL库失败的解决方法
Jan 08 Python
Django 在iframe里跳转顶层url的例子
Aug 21 Python
深入了解python中元类的相关知识
Aug 29 Python
python返回数组的索引实例
Nov 28 Python
在PyCharm中遇到pip安装 失败问题及解决方案(pip失效时的解决方案)
Mar 10 Python
Python threading.local代码实例及原理解析
Mar 16 Python
Python无损压缩图片的示例代码
Aug 06 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
乐信RP2100的电路分析和打磨
2021/03/02 无线电
JS获取IUSR_机器名和IWAM_机器名帐号的密码
2006/12/06 Javascript
用jquery写的菜单从左往右滑动出现
2014/04/11 Javascript
JS获取网页图片name属性的方法
2015/04/01 Javascript
JavaScript+html5 canvas制作的圆中圆效果实例
2016/01/27 Javascript
Wireshark基本介绍和学习TCP三次握手
2016/08/15 Javascript
Jq通过td获取同行其它列td的方法
2016/10/05 Javascript
bootstrap使用validate实现简单校验功能
2016/12/02 Javascript
vue-vuex中使用commit提交mutation来修改state的方法详解
2018/09/16 Javascript
Vue2.x中利用@font-size引入字体图标报错的解决方法
2018/09/28 Javascript
JS实现根据数组对象的某一属性排序操作示例
2019/01/14 Javascript
详解基于mpvue微信小程序下载远程图片到本地解决思路
2019/05/16 Javascript
JavaScript 扩展运算符用法实例小结【基于ES6】
2019/06/17 Javascript
vue实现滑动超出指定距离回顶部功能
2019/07/31 Javascript
js实现抽奖的两种方法
2020/03/19 Javascript
在vue中实现嵌套页面(iframe)
2020/07/30 Javascript
Python搭建FTP服务器的方法示例
2018/01/19 Python
教你用一行Python代码实现并行任务(附代码)
2018/02/02 Python
对python3中pathlib库的Path类的使用详解
2018/10/14 Python
浅谈Python批处理文件夹中的txt文件
2019/03/11 Python
python tkinter canvas 显示图片的示例
2019/06/13 Python
解决django-xadmin列表页filter关联对象搜索问题
2019/11/15 Python
python3实现绘制二维点图
2019/12/04 Python
常用python爬虫库介绍与简要说明
2020/01/25 Python
Python PyQt5整理介绍
2020/04/01 Python
python + selenium 刷B站播放量的实例代码
2020/06/12 Python
python利用opencv实现颜色检测
2021/02/23 Python
当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?
2014/09/09 面试题
会计专业毕业生自我评价
2013/09/25 职场文书
C++程序员求职信
2014/05/07 职场文书
公司自我介绍演讲稿
2014/08/21 职场文书
2014年医务科工作总结
2014/12/18 职场文书
2015元旦感言
2015/12/09 职场文书
2019年浪漫婚礼证婚词
2019/06/27 职场文书
PHP 时间处理类Carbon
2022/05/20 PHP
win10此电脑打不开怎么办 win10双击此电脑无响应的解决办法
2022/07/23 数码科技