本文讨论Python中尾递归优化以及尾递归优化原理。
本文共讨论两点内容,一个是如何进行尾递归优化,一个是递归优化原理。
- 如何进行尾递归优化
Python当中实际上没有尾递归优化的功能,递归受到栈长度限制,例如我们用递归实现斐波那契数列计算的时候,
def fib(i, current = 0, next = 1):
if i == 0:
return current
else:
return fib(i - 1, next, current + next)
当我们执行
fib(1000)
则程序会报错,因为超过了最大栈长度限制。
Traceback (most recent call last):
File "C:/test_Tail_Recursion.py", line 48, in <module>
print(fib(2000))
File "C:/test_Tail_Recursion.py", line 43, in fib
return fib(i - 1, next, current + next)
File "C:/test_Tail_Recursion.py", line 43, in fib
return fib(i - 1, next, current + next)
File "C:/test_Tail_Recursion.py", line 43, in fib
return fib(i - 1, next, current + next)
[Previous line repeated 994 more times]
File "C:/test_Tail_Recursion.py", line 40, in fib
if i == 0:
RecursionError: maximum recursion depth exceeded in comparison
所以我们需要尝试一下对尾递归进行优化。
对尾递归进行优化参考下面的代码。
Tail Call Optimization Decorator " Python recipes " ActiveState Codecode.activestate.com
但这个代码实际上是在Python2.4的环境下运行,我们需要优化为支持Python3.6环境的代码,代码如下
class TailRecurseException(BaseException):
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 decorated5
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 as e:
args = e.args
kwargs = e.kwargs
func.__doc__ = g.__doc__
return func
该函数实际上是一个装饰器,所以我们在调用的时候,只需要
@tail_call_optimized
def fib(i, current = 0, next = 1):
if i == 0:
return current
else:
return fib(i - 1, next, current + next)
即可实现函数在超过调用栈深度的情况下完成函数功能。
2. 尾递归优化原理介绍
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 as e:
args = e.args
kwargs = e.kwargs
其中
f = sys._getframe()实际上返回一个frameobject,简称f
f中的 f.f_back实际上会返回调用栈下一个元素的对象,也就是目前元素的上一个进栈对象
f.f_back.f_back顾名思义就是上上一个
f_code指的是目前代码路径
所以,当我们函数fib(1000)在装饰器下执行的时候
- fib(1000, 0, 1)在执行前首先进入装饰器函数,f.f_back为main()函数也就是调用fib的函数
- fib(1000, 0, 1)下的f.f_back.f_back为None,所以进入else逻辑(调用栈深度为1)
- 执行fib(1000, 0, 1)
- fib(1000, 0, 1)调用fib(999, 1, 1)
- 这时函数fib(999, 1, 1)进入装饰器函数,符合if条件(调用栈深度为2),执行raise TailRecurseException,并将(999, 1, 1)参数传入TailRecurseException
- 该参数被fib(1000, 0, 1)的except捕捉,通过TailRecurseException将参数(999, 1, 1)传递给fib(),从而进行下一次调用
- 重复过程4 - 6
上述为该装饰器与函数的工作过程
总结:
我们通过不停重用调用栈栈顶元素,参数通过TailRecurseException进行传递,这样栈深度最大为2,永远不会超过Python所规定的范围,从而完成我们功能的实现