如何打印递归评估过程?

时间:2014-06-08 09:47:11

标签: python

这是一个计算Fibonacci数的函数:

def fib(n):
    if n in (0, 1):
        return n
    else:
        return fib(n - 1) + fib(n - 2)
例如,

fib(3)用作

# I want this block as an output!!
fib(3)
= fib(2) + fib(1)
= (fib(1) + fib(0)) + fib(1)
= (1 + 0) + fib(1)
= 1 + fib(1)
= 1 + 1
= 2

我的问题是“是否有可能将此块(方程组)作为输出?”我认为使用traceback模块可能有可能,但无法想出任何好方法。输出不必完全采用这种格式。如果我能得到任何类似的表格,我很高兴。

2 个答案:

答案 0 :(得分:3)

一种简单的方法,没有任何内省或装饰,让球滚动:将其烘焙到功能本身:

def format_f(f):
    if isinstance(f, int) or '+' not in f:
        return "{0}".format(f)
    return "({0})".format(f)

def fib(n, first=True):
    if first:
        yield "fib({0})".format(n)
    if n < 2:
        yield n
    else:
        yield "fib({0}) + fib({1})".format(n-1, n-2)
        for f1 in fib(n-1, False):
            yield "{0} + fib({1})".format(format_f(f1), n-2)
        for f2 in fib(n-2, False):
            yield "{0} + {1}".format(f1, format_f(f2))
        yield f1 + f2

使用示例:

>>> for s in fib(3):
    print(s)


fib(3)  
fib(2) + fib(1)
(fib(1) + fib(0)) + fib(1)
(1 + fib(0)) + fib(1)
(1 + 0) + fib(1)
1 + fib(1)
1 + 1
2
>>> for s in fib(4):
    print(s)


fib(4)  
fib(3) + fib(2)
(fib(2) + fib(1)) + fib(2)
((fib(1) + fib(0)) + fib(1)) + fib(2)
((1 + fib(0)) + fib(1)) + fib(2)
((1 + 0) + fib(1)) + fib(2)
(1 + fib(1)) + fib(2)
(1 + 1) + fib(2)
2 + fib(2)
2 + (fib(1) + fib(0))
2 + (1 + fib(0))
2 + (1 + 0)
2 + 1
3

这样做的缺点是您必须访问fib(n)的实际结果值,如list(fib(n))[-1]

答案 1 :(得分:0)

在这种情况下生成不可修改代码的最简单方法可能是遵循@jonrshapre并修改函数本身。您可以使用辅助函数扩展其解决方案,包装上一个函数,以便将跟踪打印为副作用,并且仅返回实际的Fibonacci:

def fibonacci(n):
    generator = fib(3)
    final_answer = 0
    for s in fib(3):
        print(s)
        final_answer = s
return int(final_answer)

但是如果你已经考虑过包装函数,那么你应该考虑装饰器。装饰者实际上只不过是syntactic sugar for wrapping other functions。正确构造的装饰器将允许轻松地向任何功能添加一系列排序。这是第一次尝试一种可能的解决方案(输出不如你想要的那样好,但它确实显示了递归调用深度):

import functools

def trace_recursive(combine,line_length=15):
    def dec_wrapper(func):

        @functools.wraps(func)
        def func_wrapper(*args,**kargs):
            positional = [str(a) for a in args]
            keyword = ["{}={}".format(k, kargs[k]) for k in kargs]
            call = ', '.join(positional + keyword)
            call = "{}({})".format(func.__name__, call)

            func_wrapper.depth = func_wrapper.depth + 1
            func_wrapper.last_depth = func_wrapper.depth


            return_val =  func(*args,**kargs)
            if func_wrapper.depth < func_wrapper.last_depth:
                print "{}{}{}".format(func_wrapper.depth*'  ',combine,line_length*'_')

            call_and_value = "{}={:>5}".format(call,return_val)

            indent = func_wrapper.depth * '  '
            call_and_value = indent + call_and_value

            print call_and_value

            func_wrapper.depth = func_wrapper.depth - 1

            return return_val

        func_wrapper.depth = 1
        func_wrapper.last_depth = 1
        return func_wrapper

一旦有了装饰器,就可以轻松地将它应用于任何功能:

@trace_recursive('*')
def fac(n):
    if n in(0, 1):
            return 1
    else:
        return n * fac(n-1)

输出样本:

>>> fac(4)
          fac(1)=    1
        *_______________
        fac(2)=    2
      *_______________
      fac(3)=    6
    *_______________
    fac(4)=   24
24

由于您正在处理斐波那契数字,您可能需要考虑memoization,即保存先前计算的值。由于双重递归调用,天真的递归实现非常浪费,多次计算多个值。您可以将memoization实现为装饰器 - 有一个whole section at the Python Decorator LibraryActive State有一个特别好的,他们声称这可能是最快的memoization装饰器。但是我们可以为单个(可散列)参数的函数实现一个非常简单的参数:

def simple_memoize(func):
    cache = dict()

    @functools.wraps(func)
    def wrapper(arg):
        try:
            return cache[arg]
        except KeyError:
            cache[arg] = func(arg)
            return cache[arg]

    return wrapper

那么,让我们看一下没有记忆的Fibonacci函数的跟踪:

@trace_recursive('+')       
def fib(n):
    if n in (0, 1):
        return n
    else:
        return fib(n - 1) + fib(n - 2)

给你:

>>> print fib(5)
            fib(1)=    1
            fib(0)=    0
          +_______________
          fib(2)=    1
          fib(1)=    1
        +_______________
        fib(3)=    2
          fib(1)=    1
          fib(0)=    0
        +_______________
        fib(2)=    1
      +_______________
      fib(4)=    3
          fib(1)=    1
          fib(0)=    0
        +_______________
        fib(2)=    1
        fib(1)=    1
      +_______________
      fib(3)=    2
    +_______________
    fib(5)=    5
5

你可以清楚地看到每个较小的斐波纳契数都是多次计算的。如果我们添加memoization,那么我们会得到更好的结果:

@trace_recursive('+')
@simple_memoize
def fib(n):
    if n in (0, 1):
        return n
    else:
        return fib(n - 1) + fib(n - 2)

输出:

>>> print fib(5)
            fib(1)=    1
            fib(0)=    0
          +_______________
          fib(2)=    1
          fib(1)=    1
        +_______________
        fib(3)=    2
        fib(2)=    1
      +_______________
      fib(4)=    3
      fib(3)=    2
    +_______________
    fib(5)=    5
5
BTW:装饰者顺序一般来说是不可交换的。每个装饰器包裹它坐在上面的完整装饰功能,即最上面的装饰器是最外面的。我们可以在我们的示例中看到这一点:

@simple_memoize
@trace_recursive('+')
def fib(n):
    if n in (0, 1):
        return n
    else:
        return fib(n - 1) + fib(n - 2)

输出:

>>> print fib(5)
            fib(1)=    1
            fib(0)=    0
          +_______________
          fib(2)=    1
        +_______________
        fib(3)=    2
      +_______________
      fib(4)=    3
    +_______________
    fib(5)=    5
5

虽然在两种情况下,memoization都同样有效,但在内部memoization情况下,跟踪在memoization函数快捷方式缓存之前打印,而在外部memoization情况下,缓存的快捷方式完全阻止内部调用,以便永远不会调用跟踪装饰器。