python中的步长 函数 步长在python_调用栈


本文讨论Python中尾递归优化以及尾递归优化原理。

本文共讨论两点内容,一个是如何进行尾递归优化,一个是递归优化原理

  1. 如何进行尾递归优化

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)在装饰器下执行的时候

  1. fib(1000, 0, 1)在执行前首先进入装饰器函数,f.f_back为main()函数也就是调用fib的函数
  2. fib(1000, 0, 1)下的f.f_back.f_back为None,所以进入else逻辑(调用栈深度为1)
  3. 执行fib(1000, 0, 1)
  4. fib(1000, 0, 1)调用fib(999, 1, 1)
  5. 这时函数fib(999, 1, 1)进入装饰器函数,符合if条件(调用栈深度为2),执行raise TailRecurseException,并将(999, 1, 1)参数传入TailRecurseException
  6. 该参数被fib(1000, 0, 1)的except捕捉,通过TailRecurseException将参数(999, 1, 1)传递给fib(),从而进行下一次调用
  7. 重复过程4 - 6

上述为该装饰器与函数的工作过程

总结:

我们通过不停重用调用栈栈顶元素,参数通过TailRecurseException进行传递,这样栈深度最大为2,永远不会超过Python所规定的范围,从而完成我们功能的实现