after, after_cancel and Python 2.3

Edward K. Ream edreamleo at charter.net
Fri Aug 29 11:03:13 EDT 2003


Previous to Python 2.3 my app has destroyed the root Tk window using
root.destroy rather than the more usual root.quit. In Python 2.3 this does
not work so well.  In some situations (i.e., for some data), Tk (not
Tkinter) complains that an "after" routine does not exist that has been
registered with the Tkinter after routine.  Moreover, in Python 2.3, the
tkinter after_cancel routine does not appear to work at all, regardless of
whether root.destroy() or root.quit() follows after_cancel.

When using root.quit(), the after routine is called properly, so no real
harm is done.  When using root.destroy(), it appears that Tkinter (or is it
Tk) attempts to call the "after" routine after the "after" routine has
already been destroyed.

For example, the following code appears in my app's shutdown code:

    self.killed = true # Disable after events.

    # New for Python 2.3: attempt to cancel the after handler.
    if self.afterHandler != None:
        print "finishQuit: canceling",self.afterHandler
        self.root.after_cancel(self.afterHandler)
        self.afterHandler = None

    if 1: # Works in Python 2.1 and 2.2.  Leaves Python window open.
        self.root.destroy()

    else: # Works in Python 2.3.  Closes Python window.
        self.root.quit()

In versions before Python 2.3 there was never any need for a call to
root.after_cancel and the call to root.destroy never complained.  In Python
2.3, the call to root.destroy() sometimes gives an error message like this:

finishQuit: canceling after#18
>>> invalid command name "14189192callit"
    while executing
"14189192callit"
    ("after" script)

If my code calls self.root.quit() what happens is that the "after" routine
is called, sees self.killed == true and returns.  In other words, the
self.killed hack allows the after routine to work around the fact that
after_cancel has had no apparent effect.

Can anyone see any obvious problem in my code?

Here are the definitions of after and after_cancel in tkinter.py. This code
has not changed between Python 2.2 and Python 2.3.

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:
            # XXX Disgusting hack to clean up after calling func
            tmp = []
            def callit(func=func, args=args, self=self, tmp=tmp):
                try:
                    func(*args)
                finally:
                    try:
                        self.deletecommand(tmp[0])
                    except TclError:
                        pass
            name = self._register(callit)
            tmp.append(name)
            return self.tk.call('after', ms, name)

    def after_cancel(self, id):
        """Cancel scheduling of function identified with ID.

        Identifier returned by after or after_idle must be
        given as first parameter."""
        try:
            data = self.tk.call('after', 'info', id)
            # In Tk 8.3, splitlist returns: (script, type)
            # In Tk 8.4, splitlist may return (script, type) or (script,)
            script = self.tk.splitlist(data)[0]
            self.deletecommand(script)
        except TclError:
            # print "ekr: tcl error"
            pass
        # print "ekr: calling cancel",id
        self.tk.call('after', 'cancel', id)

The "disgusting" tmp hack does not appear in after_cancel.  I'm not sure
what this does, especially since tmp.append(name) would appear to have no
effect because tmp is not a global.  My guess is that if the hack is needed
in one place it might be needed in another.

Summary: For now I can just call root.quit and rely on the killed flag to
keep the after routine out of trouble, but it does appear that something
isn't quite right.  Does anyone have a clue what is going on?  Thanks.

Edward

P.S.  My "after" routine is initially created with after_idle, and
thereafter reposts itself with after.  Like this:

# An internal routine used to dispatch the "idle" hook.
def idleTimeHookHandler(*args):

    a = app()
    if a.killed: # New for Python 2.3...
        print "idleTimeHookHandler: killed"
        return

    # ...Do stuff

    # Requeue this routine after 100 msec.
    if a.idleTimeHook:
        a.afterHandler = a.root.after(a.idleTimeDelay,idleTimeHookHandler)
    else:
        a.afterHandler = None

EKR
--------------------------------------------------------------------
Edward K. Ream   email:  edreamleo at charter.net
Leo: Literate Editor with Outlines
Leo: http://webpages.charter.net/edreamleo/front.html
--------------------------------------------------------------------






More information about the Python-list mailing list