Python开启尾递归优化!

本文和大家分享的主要是python中尾递归相关使用方法,希望通过本文的分享能帮助大家更好的学习python语言,一起来看看吧。

一般递归与尾递归

一般递归

def normal_recursion(n):

if n == 1:

return 1

else:

return n + normal_recursion(n-1)

执行:

normal_recursion(5)5 + normal_recursion(4)5 + 4 + normal_recursion(3)5 + 4 + 3 + normal_recursion(2)5 + 4 + 3 + 2 + normal_recursion(1)5 + 4 + 3 + 35 + 4 + 65 + 1015

可以看到, 一般递归, 每一级递归都需要调用函数, 会创建新的栈,

随着递归深度的增加, 创建的栈越来越多, 造成爆栈:boom:

尾递归

尾递归基于函数的尾调用 , 每一级调用直接返回函数的返回值更新调用栈,而不用创建新的调用栈, 类似迭代的实现, 时间和空间上均优化了一般递归!

def tail_recursion(n, total=0):

if n == 0:

return total

else:

return tail_recursion(n-1, total+n)

执行:

tail_recursion(5)tail_recursion(4, 5)tail_recursion(3, 9)tail_recursion(2, 12)tail_recursion(1, 14)tail_recursion(0, 15)15

可以看到, 每一级递归的函数调用变成"线性"的形式.

深入理解尾递归

呃, 所以呢? 是不是感觉还不够过瘾... 谁说尾递归调用就不用创建新的栈呢?

还是让我们去底层一探究竟吧

int tail_recursion(int n, int total) {

if (n == 0) {

return total;

}

else {

return tail_recursion(n-1, total+n);

}

}

int main(void) {

int total = 0, n = 4;

tail_recursion(n, total);

return 0;

}

反汇编

·$ gcc -S tail_recursion.c -o normal_recursion.S

·$ gcc -S -O2 tail_recursion.c -o tail_recursion.S gcc开启尾递归优化

对比反汇编代码如下(AT&T语法)

可以看到, 开启尾递归优化前, 使用call调用函数, 创建了新的调用栈(LBB0_3);

而开启尾递归优化后, 就没有新的调用栈生成了, 而是直接pop

bp指向的 _tail_recursion 函数的地址(pushq %rbp)然后返回,

仍旧用的是同一个调用栈!

存在的问题

虽然尾递归优化很好, 但python 不支持尾递归,递归深度超过1000时会报错

RuntimeError: maximum recursion depth exceeded

一个牛人想出的解决办法

实现一个 tail_call_optimized 装饰器

#!/usr/bin/env python2.4# This program shows off a python decorator(# which implements tail call optimization. It# does this by throwing an exception if it is# it’s own grandparent, and catching such# exceptions to recall the stack.

import sys

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()

# 为什么是grandparent, 函数默认的第一层递归是父调用,

# 对于尾递归, 不希望产生新的函数调用(即:祖父调用),

# 所以这里抛出异常, 拿到参数, 退出被修饰函数的递归调用栈!(后面有动图分析)

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

@tail_call_optimizeddef factorial(n, acc=1):

"calculate a factorial"

if n == 0:

return acc

return factorial(n-1, n*acc)

print factorial(10000)

为了更清晰的展示开启尾递归优化前、后调用栈的变化和tail_call_optimized装饰器抛异常退出递归调用栈的作用, 我这里利用 pudb调试工具 做了动图

开启尾递归优化前的调用栈

开启尾递归优化后(tail_call_optimized装饰器)的调用栈

通过pudb右边栏的stack, 可以很清晰的看到调用栈的变化.

因为尾递归没有调用栈的嵌套, 所以Python也不会报 RuntimeError: maximum recursion depth exceeded 错误了!

这里解释一下 sys._getframe() 函数:

sys._getframe([depth]):

Return a frame object from the call stack.

If optional integer depth is given, return the frame object that many calls below the top of the stack.

If that is deeper than the call stack, ValueEfror is raised. The default for depth is zero,

returning the frame at the top of the call stack.

即返回depth深度调用的栈帧对象.

import sys

def get_cur_info():

print sys._getframe().f_code.co_filename # 当前文件名

print sys._getframe().f_code.co_name # 当前函数名

print sys._getframe().f_lineno # 当前行号

print sys._getframe().f_back # 调用者的帧

来源:SegmentFault

时间: 2024-07-30 17:44:14

Python开启尾递归优化!的相关文章

python 函数式编程尾递归优化 day16

函数编程的特征: 1不可变:不用变量保存状态,不修改变量 #非函数式 a = 1 def incr_test1(): global a#一旦更改全局变量后后面再调用a就容易乱 a += 1 return a incr_test1() print(a) def bar(): print('from bar') def foo(): print('from foo') return bar n = foo() n() return可以返回任何数值,包括自己 def hanle(): print('f

一个很Cool的Idear->Python的尾递归优化

偶然在国外一个网站瞅到的,非常的酷,发出来共享一下.一般来说,Python和Java,C#一样是没有尾递归自动优化的能力的,递归调用受到调用栈长度的限制被广泛的诟病,但是这个狂人用一个匪夷所思的方法解决了这个问题并在Python上实现了,从此Python的递归调用再也不用受到调用栈长度的制约,太酷了. 首先我们还是从递归说起,之前我发过一篇 <浅谈递归过程以及递归的优化>其中用到了斐波那契数来作为例子.线性递归的算法由于太过一低效就被我们Pass掉了,我们先来看尾递过方式的调用: 1 def 

Gcc 优化选项 与尾递归优化

今天做高性能计算机系统的作业的时候,发现gcc中的优化选项有很多应用 . 例如对于C源码: #include <stdio.h> #include <stdlib.h> int main() { int x[101],y[101]; int a,i; a = 5; for(i=0;i<=100;i++) { x[i] = i+1; y[i] = i; } for(i=100; i>=0; i--) y[i] += a*x[i]; return 0; } 1.直接用gcc

JVM原生不支持尾递归优化,但是Scala编译器支持

The JVM doesn't support TCO natively, so tail recursive methods will need to rely on the Scala compiler performing the optimization.----------"Scala in Depth" 3.5.2 Jvm本身是不支持尾递归优化得,需要编译器支持,而Java编译器不支持,但是Scala支持.写一个简单的计算1到n的和的递归算法验证一下. public cla

对SNL语言的解释器实现尾递归优化

对于SNL语言解释器的内容可以参考我的前一篇文章<使用antlr4及java实现snl语言的解释器>.此文只讲一下"尾递归优化"是如何实现的--"尾递归优化"并不是一个语言实现必须要做的,但这是一个比较有趣的东西,所以我还是想拿来讲一讲. 在前一篇文章中有一个例子: program recursion    procedure f(integer d);    begin        write(d);        f(d + 1)    endbe

.NET 4.6的RyuJIT尾递归优化的Bug

今天看到园子里有一篇新闻稿.NET 4.6的RyuJIT编译器中发现严重的Bug提到,在.Net 4.6的x64程序中默认启用新的JIT程序RyuJIT在处理尾递归指令的时候有一个Bug,导致无法得到正确的结果. 微软在其官方BlogRyuJIT Bug Advisory in the .NET Framework 4.6更是较为详细的介绍了这一bug.虽然尾递归使用得并不多(貌似在F#中有很多应用),但这个bug算是比较严重的了: 这个问题只有在应用了代码优化之后才会出现,由于多数开发者与项目

Python 代码性能优化技巧(转)

原文:Python 代码性能优化技巧 Python 代码优化常见技巧 代码优化能够让程序运行更快,它是在不改变程序运行结果的情况下使得程序的运行效率更高,根据 80/20 原则,实现程序的重构.优化.扩展以及文档相关的事情通常需要消耗 80% 的工作量.优化通常包含两方面的内容:减小代码的体积,提高代码的运行效率. 改进算法,选择合适的数据结构 一个良好的算法能够对性能起到关键作用,因此性能改进的首要点是对算法的改进.在算法的时间复杂度排序上依次是: O(1) -> O(lg n) -> O(

尾递归优化

来自廖雪峰的官方网站 fact(n)可以表示为n x fact(n-1),只有n=1时需要特殊处理. 于是,fact(n)用递归的方式写出来就是: def fact(n): if n==1: return 1 return n * fact(n - 1) 上面就是一个递归函数.可以试试: >>> fact(1) 1 >>> fact(5) 120 >>> fact(100) 933262154439441526816992388562667004907

python三级菜单优化

python三级菜单优化,菜鸟版链接:http://www.cnblogs.com/xuyaping/p/6648170.html menu = { '北京':{ '海淀':{ '五道口':{ 'soho':{}, '网易':{}, 'google':{} }, '中关村':{ '爱奇艺':{}, '汽车之家':{}, 'youku':{}, }, '上地':{ '百度':{}, }, }, '昌平':{ '沙河':{ '老男孩':{}, '北航':{}, }, '天通苑':{}, '回龙观':