Python函数跟踪

时间:2011-04-04 10:49:51

标签: python recursion

为了使递归过程更加明显,此示例为given

def fib(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fib(n - 1) + fib(n - 2)

def trace(f):
    f.indent = 0
    def g(x):
        print('|  ' * f.indent + '|--', f.__name__, x)
        f.indent += 1
        value = f(x)
        print('|  ' * f.indent + '|--', 'return', repr(value))
        f.indent -= 1
        return value
    return g


fib = trace(fib)
print(fib(4))

我可以理解跟踪功能的“什么”,但我不明白“如何”。具体做法是:

1)为什么我们有f.indent而不是简单的缩进= 0(好吧,我看到那不起作用,但我不明白为什么)。

2)我不明白如何

print('|  ' * f.indent + '|--', 'return', repr(value))
在找到值之前不会执行

有人会善意地解释整件事吗?

3 个答案:

答案 0 :(得分:7)

呼。好的,我们走了!

首先,你有一个功能,任何功能。在你的情况下,那是fib()。现在,在python中,函数也是对象,它们可以在运行时创建,所以我们实际上可以这样做:

def give_me_a_function():
    def f(x):
        return x

    return f

(警告:对于此答案的其余部分,可能会重复“功能”这个词。)

好吧,我们定义了一个不带参数和返回的函数,......另一个函数?那就对了!功能是对象!您可以在运行时创建它们!因此,我们在原始函数中定义了第二个函数,并将其返回,就像我们对任何其他对象一样。

现在,让我们做一些更复杂的事情:

def alter(other_function):
    def altered(x):
        return other_function(x) + 1

    return altered

那到底是什么意思?

好吧,我们定义了一个函数alter()。就像上面的例子一样,它在运行时创建一个函数并将其作为对象返回。我们已经覆盖了很多。

现在,如果函数是对象,并且可以创建并返回,为什么你不能传递一个作为参数?打电话给它,你就是它!这是正确的:alter()将函数作为参数(*),并使用它。

alter()将上述魔法与这个新魔法结合起来所需要的只是:我们接收一个函数作为参数,动态创建另一个使用它的函数,然后返回这个新的函数对象!

我们试一试。

>>> def f(x):
...     return 2*x
>>> new_function = alter(f)
>>> f(2)
4
>>> new_function(2)
5

有它! alter()获取f(),创建一个返回f() + 1的新函数,并将其作为返回值提供给我。我将它分配给new_function,我有一个新的,自制的,运行时创建的函数。

(我确实警告过你使用'功能'一词,不是吗?)

现在,到你的代码。你做的事情比f() + 1更复杂。或不?好吧,你正在创建一个新函数,它接受原始函数,调用它并打印一些数据。这并不比我们刚刚做的那么神奇。哪个区别很大?

嗯,有一个细节:fib()是递归的,所以它自称,对吧?不!不是本身。它调用fib(),你碰巧这样做了:

fib = trace(fib)

<强> WHAM。 fib()已经不再存在了!现在fib()trace(fib)!因此,当fib()进入递归时,它不会调用自身,而是调用我们自己创建的包装版本。

这就是缩进被这样处理的原因。再看看trace(),现在知道它实际上是递归缩进的,这是有意义的,不是吗?你希望每个递归级别都有一个缩进,所以递增它,调用fib()(记住,现在是trace(fib)),然后当我们回来时(所以递归来了,我们将要回到调用链中的上一步)我们减少它。

如果您仍然没有看到它,请尝试将所有功能移至fib()。忘记装饰功能,这简直令人困惑。

阿。我真的希望这会有所帮助,那些击败我的答案的那些人已经没有把这个问题过时了。

干杯!

(*)是啊是啊鸭子打字yadda yadda可调用对象bla bla无关紧要。

答案 1 :(得分:4)

  1. 如果我们只使用indent代替f.indent,它将成为内部函数g() 中的局部变量,因为indent中对g()的分配。由于这似乎是Python 3,实际上没有必要使用函数属性 - 您也可以使用nonlocal关键字:

    def trace(f):
        indent = 0
        def g(x):
            nonlocal indent
            print('|  ' * indent + '|--', f.__name__, x)
            indent += 1
            value = f(x)
            print('|  ' * indent + '|--', 'return', repr(value))
            indent -= 1
            return value
        return g
    
  2. 在至少调用print()一次之前,将无法进行第二次f()调用。它出现在之后f()的调用之后,因此执行流程只会在f()返回后才会到达。

答案 2 :(得分:1)

如果要将缩进级别存储在indent中,它将是当前函数调用的本地级别。每次调用该函数时,您都会得到一个值为0的新变量。通过将它存储在函数对象中,每个函数调用都是相同的(函数也是python中的对象)。

对于第二部分,我不确定你在问什么。只要参数大于1,就会对fib进行两次新调用,因此不会返回任何值。在参数等于1或0之前,将进行返回调用。