Tkinter的.after()和递归

时间:2017-06-22 22:23:43

标签: python python-3.x recursion tkinter

要及时更新窗口小部件,我使用.after()方法,通常采用以下格式:

def update():
    do_something()
    <widget>.after(<delay>, update)

据我所知,小部件会等待一段时间,然后执行update()函数,小部件会在重新执行函数之前再次等待,等等。

这在我看来很像递归。所以,问题是:.after()实际上是否通过递归工作?

如果确实如此,那么递归的深度是有限制的,但是下面的例子应该证明永远不会达到这样的限制:

from tkinter import *

counter = 0

def count():
    global counter
    counter += 1
    lbl.config(text=counter)
    root.after(10, count)

root = Tk()
lbl = Label(root, text='0')
lbl.pack()
Button(root, text='Start count', command=count).pack()
root.mainloop()

在我的系统中,递归深度的限制是1000,但是这个例子在几秒钟内远远超过了该值,直到我停止它为止。

4 个答案:

答案 0 :(得分:5)

递归意味着函数的当前实例处于保持状态,并创建并运行新实例。 after的工作方式不同,不是递归。

您可以将mainloop视为维护待办事项列表的无限循环。该列表具有应该运行的功能和时间。 mainloop不断检查todo列表,如果要运行todo列表中的项目,则mainloop将从列表中删除该项目并运行它。完成后,它将返回循环并检查列表。 after方法只是为此待办事项列表添加了一个函数以及运行它的时间。

答案 1 :(得分:2)

  

据我所知,小部件会等待一段时间,然后执行update()函数,最后小部件会在重新执行函数之前再次等待。

突出显示的部分为false。 after只是将函数放在队列中。它不会重新执行任何操作。 mainloop只是将“after”队列中的内容弹出并运行一次。

  

所以,问题是:.after()实际上是否通过递归工作?

没有。 after应该已命名为add_job_to_queue。它不是递归,它只是将一个作业放在队列上。

  

如果确实如此,那么递归的深度是有限制的,但是下面的例子应该证明永远不会达到这样的限制:

def count():
    global counter
    counter += 1
    lbl.config(text=counter)
    root.after(10, count)

再次,没有达到限制的原因是因为它不是递归。当您通过单击按钮调用count时,它会执行一些操作,然后将一个项目添加到“之后”队列中。队列的长度现在是一个。

到时候,mainloop会将该项目从队列中弹出,使队列的长度为零。然后,您的代码将自己添加到队列中,使长度为1。到时候,mainloop会将该项目从队列中弹出,使队列的长度为零。然后,......

答案 2 :(得分:1)

在你的例子中根本没有递归,因为count()没有从它自己调用(你只是告诉Tk它需要在10ms之后调用你的函数)但是由Tk的主循环调用;)。 / p>

答案 3 :(得分:-1)

看一下python源代码,我不认为<table id="myTable"> <tbody> <tr> <td> <select id="variableList" class="select"> <option value="" disabled selected>Please choose...</option> <option>Var 1</option> <option>Var 2</option> <option>Var 3</option> <option value="Other">Other...</option> </select> </td> </tr> <tr id="txtBoxRow"> <td> <textarea id="newVariable"></textarea> </td> </tr> <tr> <td> <input type="email" name="email" id="emailID" placeholder="Your email address..."> </td> </tr> <tr> <td> <button class="buttons" onclick="sendEmail()">Email</button> </td> </tr> </tbody> </table>递归地工作。我使用.after库启动新线程。

Tcl

Modules / _tk​​inter.c:注册该函数并调用它。 def after(self, ms, func=None, *args): """Call function once after given time. MS specifies the time in milliseconds. FUNC gives the function which shall be called. Additional parameters are given as parameters to the function call. Return identifier to cancel scheduling with after_cancel.""" if not func: # I'd rather use time.sleep(ms*0.001) self.tk.call('after', ms) else: def callit(): try: func(*args) finally: try: self.deletecommand(name) except TclError: pass callit.__name__ = func.__name__ name = self._register(callit) return self.tk.call('after', ms, name) 类是内置的类,也位于同一个文件中。 API通过调用Tcl库函数来工作。

限制在Tk的函数是tk.call

Tkapp_Call

此函数的注释说明这只是调用{"call", Tkapp_Call, METH_VARARGS}, 函数。

Tcl

此外,在函数结束时调用参数时会释放参数:/* This is the main entry point for calling a Tcl command. It supports three cases, with regard to threading: 1. Tcl is not threaded: Must have the Tcl lock, then can invoke command in the context of the calling thread. 2. Tcl is threaded, caller of the command is in the interpreter thread: Execute the command in the calling thread. Since the Tcl lock will not be used, we can merge that with case 1. 3. Tcl is threaded, caller is in a different thread: Must queue an event to the interpreter thread. Allocation of Tcl objects needs to occur in the interpreter thread, so we ship the PyObject* args to the target thread, and perform processing there. */ ,因此如果递归使用参数,则在1次调用后它们不会被释放。