Python 函数跟踪

发布于 2024-10-30 04:05:56 字数 822 浏览 1 评论 0原文

为了让递归的过程更加直观,给出这个例子给出

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 而不是简单的 indent = 0 (嗯,我发现这不起作用,但我不明白为什么)。

2)我不明白如何

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

在找到值之前不执行。

有好心人能彻底解释一下整个事情吗?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

往事随风而去 2024-11-06 04:05:57

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

对于第二部分,我不太确定你在问什么。每当参数大于 1 时,就会对 fib 进行两次新调用,因此不会返回任何值。直到参数等于 1 或 0 为止,才会进行返回调用。

If you would store the indent level just in indent, it would be local to the current function call. Each time the function is called, you would get a new variable, with the value 0. By storing it in the function object, it will be the same for each function call (function are objects too in python).

For the second part, I'm not really sure what you are asking. Whenever the argument is greater then 1, two new calls to fib are made, and thus, no value is returned. Until the argument equals 1 or 0, a return call is made.

守护在此方 2024-11-06 04:05:56

呼。好吧,我们开始吧!

首先,你有一个函数,任何函数。在你的例子中,那就是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()。忘记装饰功能,这很令人困惑。

啊。我真的希望这对你有所帮助,并且 2000 个人比我先找到答案并没有让这个问题变得过时。

干杯!

(*) 是的,是的,鸭子输入 yadda yadda 可调用对象 bla bla 不相关。

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.

掐死时间 2024-11-06 04:05:56
  1. 如果我们只使用 indent 而不是 f.indent,它将成为内部函数 g()内的局部变量 code>,由于在 g() 中对 indent 进行了赋值。由于这似乎是 Python 3,因此实际上没有必要使用函数属性 - 您还可以使用 nonlocal 关键字:

    def 跟踪(f):
        缩进=0
        定义 g(x):
            非局部缩进
            print('| ' * 缩进 + '|--', f.__name__, x)
            缩进 += 1
            值 = f(x)
            print('| ' * 缩进 + '|--', 'return', repr(value))
            缩进 -= 1
            返回值
        返回g
    

  2. 第二个 print()在至少一次 f() 调用返回之前,不会到达调用。它出现在调用 f() 之后的代码中,因此执行流程只有在 f() 返回后才会到达那里。

  1. If we would just use indent instead of f.indent, it would become a local variable inside the inner function g(), due to the assignments to indent in g(). Since this seems to be Python 3, it is actually not necessary to use a function attribute -- you could also use the nonlocal keyword:

    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. The second print() call won't be reached before at least one invocation of f() has returned. It appears in the code after the call to f(), so the flow of execution will only get there after f() has returned.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文