Python 函数跟踪 [英] Python Function Tracing

查看:50
本文介绍了Python 函数跟踪的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

为了使递归过程更加可见,这个例子是给出的::>

def fib(n):如果 n == 0:返回 0elif n == 1:返回 1别的:返回 fib(n - 1) + fib(n - 2)定义跟踪(f):f.indent = 0定义 g(x):print('| ' * f.indent + '|--', f.__name__, x)f.缩进 += 1值 = f(x)print('| ' * f.indent + '|--', 'return', repr(value))f.indent -= 1返回值返回 gfib = 跟踪(fib)打印(fib(4))

我可以理解跟踪功能的什么",但我不理解如何".具体:

1) 为什么我们有 f.indent 而不是简单的 indent = 0(嗯,我看到这行不通,但我不明白为什么).

2) 我不明白如何

print('| ' * f.indent + '|--', 'return', repr(value))

在找到值之前不会执行.

有人愿意彻底解释整个事情吗?

解决方案

哇.好的,我们开始!

首先,你有一个函数,任何函数.就您而言,就是 fib().现在,在python中,函数也是对象,它们可以在运行时创建,所以我们实际上可以这样做:

def give_me_a_function():定义 f(x):返回 x返回 f

(警告:在本答案的其余部分中功能"这个词的重复可怕).

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

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

def alter(other_function):定义改变(x):返回 other_function(x) + 1返回更改

那是什么鬼?

好吧,我们定义了一个函数,alter().就像在上面的例子中一样,它在运行时创建一个函数并将它作为对象返回.我们已经介绍了这么多.

现在,如果函数是对象,并且可以创建和返回,为什么不能将一个作为参数传递?并调用它,当你在它的时候!没错:alter() 接受一个函数作为参数(*),并使用它.

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

让我们试试吧.

<预><代码>>>>定义 f(x):...返回 2*x>>>new_function = 改变(f)>>>f(2)4>>>new_function(2)5

就这样!alter() 接受我的 f(),创建一个将返回 f() + 1 的新函数,并将其作为返回值.我将它分配给 new_function,并且我有一个新的、自制的、运行时创建的函数.

(我确实警告过你使用函数"这个词,不是吗?)

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

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

fib = trace(fib)

什么.fib() 不再是它自己了! 现在 fib()trace(fib)!因此,当 fib() 进入递归时,它不是在调用自身,而是在调用我们创建的自身的包装版本.

这就是为什么缩进是这样处理的.再看看 trace() ,现在知道它实际上是递归缩进,这是有道理的,不是吗?你希望每一级递归都有一个缩进,所以增加它,调用 fib()(记住,现在是 trace(fib)),然后当我们'回来了(所以递归来来去去,我们将要返回到调用链中的上一步)我们递减它.

如果您仍然没有看到,请尝试将所有功能移至 fib().忘记装饰功能吧,这很容易让人困惑.

啊.我真的希望这会有所帮助,并且击败我的 2.000 人并没有让这个问题过时.

干杯!

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

To make the process of recursion more visible, this example is 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))

I can understand "what" the trace function does, but I don't understand "how". Specifically:

1) Why do we have f.indent instead of, say, simple indent = 0 (well, I see that that doesn't work, but I don't see why).

2) I don't understand how

print('|  ' * f.indent + '|--', 'return', repr(value))

is not executed until a value is found.

Would somebody be kind enough to explain the whole thing thoroughly?

解决方案

Whew. All right, here we go!

First, you have a function, any function. In your case, that's fib(). Now, in python, functions are also objects, and they can be created in runtime, so we can actually do this:

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

    return f

(Warning: horrible repetition of the word 'function' for the rest of this answer).

Well, we defined a function that takes no arguments and returns,... another function? That's right! Functions are objects! You can create them in runtime! So we defined a second function inside our original one, and returned it, as we would any other object.

Now, let's do something a tad more complicated:

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

    return altered

What the hell was that?

Well, we defined a function, alter(). Just as in the example above, it creates a function in run-time and returns it, as the object it is. That much we already covered.

Now, if functions are objects, and can be created and returned, why wouldn't you be able to pass one as argument? And call it, while it you're at it! That's right: alter() takes a function as argument(*), and uses it.

All it takes to have alter() is combining the above magic with this new magic: we receive a function as an argument, create another one on the fly that makes use of it, and return this new function-object!

Let's try it.

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

There it goes! alter() takes my f(), creates a new function that will return f() + 1, and gives that to me as a return value. I assign it to new_function, and I have a new, home-brewed, run-time created function.

(I did warn you about the use of the word 'function', did I not?)

Now, to your piece of code. You're doing something more complicated than just f() + 1. Or not? Well, you're creating a new function that takes the original one, calls it, and prints some data. That's not much more magical than what we just did. Where's the big difference?

Well, there is one detail: fib() is recursive, so it calls itself, right? Nope! Not itself. It calls fib(), and you happened to do this:

fib = trace(fib)

WHAM. fib() is not itself anymore! Now fib() is trace(fib)! So when fib() goes into recursion, it's not calling itself, it's calling the wrapped version of itself we created.

That's why the indentation is handled like that. Look at trace() again, now knowing it's actually recursively indenting, and it makes sense, doesn't it? You want to have one indentation per level of recursion, so increment it, call fib() (which, remember, is now trace(fib)), and then when we're back (so the recursion went and came, and we're about to return to a previous step in the calling chain) we decrement it.

If you still don't see it, try moving all the functionality to fib(). Forget about the decorating function, that's plain confusing.

Ah. I really hope this helps, and that the 2.000 guys that beat me to the answer didn't already make this question obsolete.

Cheers!

(*) Yeah yeah duck typing yadda yadda callable objects bla bla irrelevant.

这篇关于Python 函数跟踪的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆