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检测是文件还是目录的方法
Jul 03 Python
Python使用QQ邮箱发送Email的方法实例
Feb 09 Python
python添加模块搜索路径方法
Sep 11 Python
如何在sae中设置django,让sae的工作环境跟本地python环境一致
Nov 21 Python
Python3实现的Mysql数据库操作封装类
Jun 06 Python
Linux下python制作名片示例
Jul 20 Python
python实现对服务器脚本敏感信息的加密解密功能
Aug 13 Python
python实现kNN算法识别手写体数字的示例代码
Aug 16 Python
Python random模块制作简易的四位数验证码
Feb 01 Python
有趣的Python图片制作之如何用QQ好友头像拼接出里昂
Apr 22 Python
tensorflow模型的save与restore,及checkpoint中读取变量方式
May 26 Python
TensorFlow的自动求导原理分析
May 26 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中str_replace函数使用小结
2008/10/11 PHP
php中日期加减法运算实现代码
2011/12/08 PHP
php上传图片存入数据库示例分享
2014/03/11 PHP
PHP开发微信支付的代码分享
2014/05/25 PHP
PHP实现指定字段的多维数组排序函数分享
2015/03/09 PHP
Yii实现Command任务处理的方法详解
2016/07/14 PHP
php 人员权限管理(RBAC)实例(推荐)
2017/05/24 PHP
JavaScript 创建对象和构造类实现代码
2009/07/30 Javascript
JQuery与iframe交互实现代码
2009/12/24 Javascript
javascript中的继承实例代码
2011/04/27 Javascript
深入理解JavaScript系列(8) S.O.L.I.D五大原则之里氏替换原则LSP
2012/01/15 Javascript
JS图片无缝、平滑滚动代码
2014/03/11 Javascript
jQuery实现切换页面过渡动画效果
2015/10/29 Javascript
学习使用AngularJS文件上传控件
2016/02/16 Javascript
详细总结Javascript中的焦点管理
2016/09/17 Javascript
Bootstrap时间选择器datetimepicker和daterangepicker使用实例解析
2016/09/17 Javascript
JS控制FileUpload的上传文件类型实例代码
2016/10/07 Javascript
angular实现图片懒加载实例代码
2017/06/08 Javascript
使用vuex解决刷新页面state数据消失的问题记录
2019/05/08 Javascript
在Python运行时动态查看进程内部信息的方法
2019/02/22 Python
Python生成指定数量的优惠码实操内容
2019/06/18 Python
Python 实现文件读写、坐标寻址、查找替换功能
2019/09/11 Python
python使用 __init__初始化操作简单示例
2019/09/26 Python
为什么说Python可以实现所有的算法
2019/10/04 Python
python requests模拟登陆github的实现方法
2019/12/26 Python
pycharm 中mark directory as exclude的用法详解
2020/02/14 Python
Java byte数组操纵方式代码实例解析
2020/07/22 Python
详解python中的异常捕获
2020/12/15 Python
Bose加拿大官方网站:美国知名音响品牌
2019/03/21 全球购物
北美女性服装零售连锁店:maurices
2019/06/12 全球购物
大学三年的自我评价
2013/12/25 职场文书
村委会贫困证明
2014/01/14 职场文书
环保证明
2015/06/23 职场文书
MySQL触发器的使用
2021/05/24 MySQL
Windows 11上手初体验:任务栏和开始菜单等迎来大改
2021/11/21 数码科技
python获取字符串中的email
2022/03/31 Python