Python 函数跟踪
为了让递归的过程更加直观,给出这个例子给出:
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
如果您将缩进级别存储在
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.
呼。好吧,我们开始吧!
首先,你有一个函数,任何函数。在你的例子中,那就是
fib()
。现在,在Python中,函数也是对象,它们可以在运行时创建,所以我们实际上可以这样做:(警告:在这个答案的其余部分中可怕地重复“函数”这个词)。
好吧,我们定义了一个不带参数并返回的函数,...另一个函数?这是正确的!函数就是对象!您可以在运行时创建它们!因此,我们在原始函数中定义了第二个函数,然后返回它,就像我们返回任何其他对象一样。
现在,让我们做一些更复杂的事情:
那到底是什么?
好吧,我们定义了一个函数,
alter()
。就像上面的例子一样,它在运行时创建一个函数并返回它,因为它是对象。我们已经介绍了这么多。现在,如果函数是对象,并且可以创建和返回,为什么不能将其作为参数传递呢?当你在做的时候就打电话吧!没错:
alter()
将函数作为参数 (*),并使用它。alter()
所需要做的就是将上述魔法与这个新魔法结合起来:我们接收一个函数作为参数,动态创建另一个使用它的函数,然后返回这个新函数-目的!我们来试试吧。
就这样!
alter()
接受我的f()
,创建一个将返回f() + 1
的新函数,并将其作为返回值。我将其分配给new_function
,并且我有一个新的、自制的、运行时创建的函数。(我确实警告过您有关“函数”一词的使用,不是吗?)
现在,看您的代码。您正在做的事情比
f() + 1
更复杂。或不?好吧,您正在创建一个新函数,它接受原始函数,调用它,并打印一些数据。这并不比我们刚才所做的更神奇。最大的区别在哪里?嗯,有一个细节:fib() 是递归的,所以它会调用自身,对吧?没有!不是本身。它调用
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:(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:
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.
There it goes!
alter()
takes myf()
, creates a new function that will returnf() + 1
, and gives that to me as a return value. I assign it tonew_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:WHAM.
fib()
is not itself anymore! Nowfib()
istrace(fib)
! So whenfib()
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, callfib()
(which, remember, is nowtrace(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.
如果我们只使用
indent
而不是f.indent
,它将成为内部函数g()内的局部变量 code>,由于在
g()
中对indent
进行了赋值。由于这似乎是 Python 3,因此实际上没有必要使用函数属性 - 您还可以使用nonlocal
关键字:第二个
print()
在至少一次f()
调用返回之前,不会到达调用。它出现在调用f()
之后的代码中,因此执行流程只有在f()
返回后才会到达那里。If we would just use
indent
instead off.indent
, it would become a local variable inside the inner functiong()
, due to the assignments toindent
ing()
. Since this seems to be Python 3, it is actually not necessary to use a function attribute -- you could also use thenonlocal
keyword:The second
print()
call won't be reached before at least one invocation off()
has returned. It appears in the code after the call tof()
, so the flow of execution will only get there afterf()
has returned.