'Tkinter's .after() and recursion
To update a widget in time I use the .after()
method, usually in the following form:
def update():
do_something()
<widget>.after(<delay>, update)
It is my understanding that the widget waits for a certain amount of time and then executes the update()
function, at the end of which the widget waits once again before re-executing the function and so on.
This seems to me a lot like recursion. So, the question is: Does .after()
actually work by means of recursion or not?
If it does, then there is a limit to the depth of recursion, but the following example should prove that such limit is never reached:
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()
In my system the limit to the depth of recursion is 1000, but this example goes far beyond that value in a few seconds until I stop it.
Solution 1:[1]
It is my understanding that the widget waits for a certain amount of time and then executes the update() function, at the end of which the widget waits once again before re-executing the function and so on.
The highlighted section is false. after
simply places the function on a queue. It doesn't re-execute anything. mainloop
simply pops things off of the "after" queue and runs them once.
So, the question is: Does .after() actually work by means of recursion or not?
No. after
should have been named add_job_to_queue
. It isn't recursion, it simply places a job on a queue.
If it does, then there is a limit to the depth of recursion, but the following example should prove that such limit is never reached:
def count():
global counter
counter += 1
lbl.config(text=counter)
root.after(10, count)
The reason no limit is reached is, again, because it's not recursion. When you call count
by clicking on a button, it does some work and then it adds one item to the "after" queue. The length of the queue is now one.
When the time comes, mainloop
will pop that item off of the queue, making the queue have a length of zero. Then, your code adds itself to the queue, making the length one. When the time comes, mainloop
will pop that item off the queue, making the queue have a length of zero. Then, ...
Solution 2:[2]
There's no recursion at all in your example, since count()
is not called from itself (you're just telling Tk that it needs to call your function after 10ms) but invoked by Tk's main loop ;).
Solution 3:[3]
in my program
keyboard=tk.Tk()
def readsm_s():
...
keyboard.after(30, readsm_s)
readsm_s() is recalled many time, after that there is a error 'maximum recursion depth exceeded while calling a Python object'
find python the default depth of recursion is limited. ? the default is 1000 ?
Solution 4:[4]
Took a look at the python source code, I don't think .after
works recursively. I starts new threads using the Tcl
library.
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)
Modules/_tkinter.c: Registers the function and calls it. The Tk
class is builtin class also located in the same file. The API works by calling Tcl library functions.
The function bounded to tk.call
is Tkapp_Call
:
{"call", Tkapp_Call, METH_VARARGS},
The comments for this function explain that this just calls Tcl
functions.
/* 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. */
Additionally, the arguments are freed when this is called at the end of the function: Tkapp_CallDeallocArgs(objv, objStore, objc);
, so if the arguments are recursively used, they would not have been freed after 1 call.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Bryan Oakley |
Solution 2 | cdonts |
Solution 3 | lam vu Nguyen |
Solution 4 | BrockLee |