Ответ 1
На самом деле это неинтерактивный интерпретатор, который здесь имеет значение, но ждет ввода в TTY. Вы можете получить такое же поведение от script следующим образом:
import tkinter
t = tkinter.Tk()
input()
(В Windows вам, возможно, придется запускать script в pythonw.exe вместо python.exe, но в противном случае вам не нужно ничего особенного делать.)
Итак, как это работает? В конечном счете трюк сводится к PyOS_InputHook
- так же работает модуль readline
.
Если stdin является TTY, то каждый раз, когда он пытается получить строку с input()
, различные биты модуля code
, встроенный REPL и т.д., Python вызывает любые установленные PyOS_InputHook
просто чтение из stdin.
Вероятно, легче понять то, что readline
делает: он пытается select
на stdin или аналогичный цикл, для каждого нового символа ввода, или каждые 0,1 секунд или любого сигнала.
То, что Tkinter
делает, похоже. Это сложнее, потому что он имеет дело с Windows, но на * nix он делает что-то очень похожее на readline
. За исключением того, что он вызывает Tcl_DoOneEvent
каждый раз через цикл.
И это ключ. Повторное вызов Tcl_DoOneEvent
- это то же самое, что mainloop
.
(Threads все усложняет, конечно, но предположим, что вы не создали никаких фоновых потоков. В вашем реальном коде, если вы хотите создать фоновые потоки, у вас будет только поток для всех Tkinter
материал, который блокирует на mainloop
в любом случае, правильно?)
Итак, если ваш код Python тратит большую часть времени на вход TTY (как обычно это делает интерактивный интерпретатор), интерпретатор Tcl прерывается, и ваш графический интерфейс отвечает. Если вы сделаете блок интерпретатора Python чем-то другим, кроме ввода TTY, интерпретатор Tcl не будет запущен и ваш графический интерфейс не будет отвечать.
Что делать, если вы хотите сделать то же самое вручную в чистом коде Python? Вам нужно сделать это, если хотите, например, интегрировать графический интерфейс Tkinter и сетевой клиент на основе select
в однопоточное приложение, верно?
Это легко: проведите один цикл от другого.
Вы можете select
с таймаутом 0,02 с (тот же самый тайм-аут, который используется по умолчанию для ввода), и вызывать t.dooneevent(Tkinter.DONT_WAIT)
каждый раз через цикл.
Или, в качестве альтернативы, вы можете разрешить Tk-накопителю, вызвав mainloop
, но используйте after
и друзей, чтобы убедиться, что вы вызываете select
достаточно часто.