Python中使用装饰器来优化尾递归的示例
作者:cangmean 发布时间:2023-09-19 21:39:07
标签:Python,尾递归
尾递归简介
尾递归是函数返回最后一个操作是递归调用,则该函数是尾递归。
递归是线性的比如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图像处理之gif动态图的解析与合成操作。分享给大家供大家参考,具体如下:gif动态图是在现在已经司空见惯,朋友圈里
- 程序很简单,主要是 mp3play 模块的应用import mp3play, timefilename = "Should It
- 1、简要说明结巴分词支持三种分词模式,支持繁体字,支持自定义词典2、三种分词模式全模式:把句子中所有的可以成词的词语都扫描出来, 速度非常快
- 组合模式是把一个类别归为一个整体,并且组织多个整体之间的关系,使用通过树形结构来描述所有整体。一般的编写方式为一个类别对象下添加多个该对象的
- 一、os模块os.getcwd() 获取当前的工作目录os.chdir(‘绝对路径/相对于当前工作目录的路径&r
- 1. 前缀索引与全部索引概念怎么给字符串字段加索引?现在,几乎所有的系统都支持邮箱登录,如何在邮箱这样的字段上建立合理的索引,是我们今天要讨
- provide和inject的用法(vue组件爷孙传值)聊聊概念成对出现:provide和inject是成对出现的作用:用于父组件向子孙组件
- 对于一些复杂的hdf5文件,通过可视化的方法可以比较容易的了解文件的内部结构,下面介绍基于python的一个hdf5文件的安装使用方法1 安
- 目录1.代码的编写过程2.结果的可视化展示3.结论前言:前不久,我刷到这样一条短视频,“1.7亿的90后仅有约1000万对结婚,结婚率不到1
- 1. RequestDispatcher.forward()在服务器端起作用,当使用forward()时,Servlet engine传递H
- 如下所示:enc = OneHotEncoder(categorical_features=np.array([0,1,2]),n_valu
- ./当前目录 /网站主目录 ../上层目录 ~/网站虚拟目录 如果当前的网站目录为E:\wwwroot 应用程序虚拟目录为E:\wwwroo
- 一、爬取数据话不多说了,直接上代码( copy即可用 )import requestsimport pandas as pdclass Sp
- 开发测试的时候,经常需要填写姓名、手机号、身份证号、银行卡号,既要符合格式要求、又不能重复。大家会到网上搜各种生成器。下面小编自己写的一个j
- 简介Button(按钮)组件用于实现各种各样的按钮。Button 组件可以包含文本或图像,你可以将一个 Python 的函数或方法与之相关联
- 本文实例为大家分享了微信小程序实现登陆注册滑块验证的具体代码,供大家参考,具体内容如下一、创建自定义组件MoveVerifyMoveVeri
- 一、断点续传原理所谓断点续传,也就是要从文件已经下载的地方开始继续下载。在以前版本的 HTTP 协议是不支持断点的,HTTP/1.1 开始就
- 前言实现类似SQL的join操作,通过pd.merge()方法可以自由灵活地操作各种逻辑的数据连接、合并等操作可以将两个DataFrame或
- 什么多态:同一事物有多种形态为何要有多态=》多态会带来什么样的特性,多态性多态性指的是可以在不考虑对象具体类型的情况下而直接使用对象多态指的
- 使用 Appium安装一下 Python 用到的模块pip install Appium-Python-Client获取好友列表在 Pych