What magic prevents Tkinter programs from blocking in interactive shell?
Question: _Note: This is somewhat a follow-up on the question:[Tkinter - when do I need
to call mainloop?](http://stackoverflow.com/questions/8683217/tkinter-when-do-
i-need-to-call-mainloop)_

Usually when using [Tkinter](http://docs.python.org/3/library/tkinter.html),
you call [Tk.mainloop](http://www.tcl.tk/man/tcl8.5/TkLib/MainLoop.htm) to run
the event loop and ensure that events are properly processed and windows
remain interactive without blocking.

When using Tkinter from within an interactive shell, running the main loop
does not seem necessary. Take this example:

    
    
    >>> import tkinter
    >>> t = tkinter.Tk()
    

A window will appear, and it will not block: You can interact with it, drag it
around, and close it.

So, something in the interactive shell does seem to recognize that a window
was created and runs the event loop in the background.

Now for the interesting thing. Take the example from above again, but then in
the next prompt (without closing the window), enter anything—without actually
executing it (i.e. don’t press enter). For example:

    
    
    >>> t = tkinter.Tk()
    >>> print('Not pressing enter now.') # not executing this
    

If you now try to interact with the Tk window, you will see that it completely
_blocks_. So the event loop which we thought would be running in the
background stopped while we were entering a command to the interactive shell.
If we send the entered command, you will see that the event loop _continues_
and whatever we did during the blocking will continue to process.

So the big question is: **What is this magic that happens in the interactive
shell?** What runs the main loop when we are not doing it explicitly? And why
does it need to halt when we _enter_ commands (instead of halting when we
execute them)?

_Note:_ The above works like this in the command line interpreter, not IDLE.
As for IDLE, I assume that the GUI won’t actually tell the underlying
interpreter that something has been entered but just keep the input locally
around until it’s being executed.


Answer: It's actually not being an interactive interpreter that matters here, but
waiting for input on a TTY. You can get the same behavior from a script like
this:

    
    
    import tkinter
    t = tkinter.Tk()
    input()
    

(On Windows, you may have to run the script in pythonw.exe instead of
python.exe, but otherwise, you don't have to do anything special.)

* * *

So, how does it work? Ultimately, the trick comes down to `PyOS_InputHook`—the
same way the `readline` module works.

If stdin is a TTY, then, each time it tries to fetch a line with `input()`,
various bits of the `code` module, the built-in REPL, etc., Python calls any
installed `PyOS_InputHook` instead of just reading from stdin.

It's probably easier to understand [what `readline`
does](http://hg.python.org/cpython/file/045e7a587f3c/Modules/readline.c#l983):
it tries to `select` on stdin or similar, looping for each new character of
input, or every 0.1 seconds, or every signal.

[What `Tkinter`
does](http://hg.python.org/cpython/file/045e7a587f3c/Modules/_tkinter.c#l3051)
is similar. It's more complicated because it has to deal with Windows, but on
*nix it's doing something pretty similar to `readline`. Except that it's
calling `Tcl_DoOneEvent` each time through the loop.

And that's the key. Calling `Tcl_DoOneEvent` repeatedly is exactly the same
thing that
[`mainloop`](http://hg.python.org/cpython/file/045e7a587f3c/Modules/_tkinter.c#l2581)
does.

(Threads make everything more complicated, of course, but let's assume you
haven't created any background threads. In your real code, if you want to
create background threads, you'll just have a thread for all the `Tkinter`
stuff that blocks on `mainloop` anyway, right?)

* * *

So, as long as your Python code is spending most of its time blocked on TTY
input (as the interactive interpreter usually is), the Tcl interpreter is
chugging along and your GUI is responding. If you make the Python interpreter
block on something other than TTY input, the Tcl interpreter is not running
and the your GUI is not responding.

* * *

What if you wanted to do the same thing manually in pure Python code? You'd of
need to do that if you want to, e.g., integrate a Tkinter GUI and a
`select`-based network client into a single-threaded app, right?

That's easy: Drive one loop from the other.

You can `select` with a timeout of 0.02s (the same timeout the default input
hook uses), and call `t.dooneevent(Tkinter.DONT_WAIT)` each time through the
loop.

Or, alternatively, you can let Tk drive by calling `mainloop`, but use `after`
and friends to make sure you call `select` often enough.

