python - ¿Qué magia evita que los programas Tkinter se bloqueen en la shell interactiva?
interactive-shell (1)
En realidad, no es un intérprete interactivo lo que importa aquí, sino la espera de comentarios sobre un TTY. Puede obtener el mismo comportamiento de un script como este:
import tkinter
t = tkinter.Tk()
input()
(En Windows, puede que tenga que ejecutar el script en pythonw.exe en lugar de python.exe, pero de lo contrario, no tiene que hacer nada especial).
¿Entonces, cómo funciona? En última instancia, el truco se reduce a PyOS_InputHook
, de la misma manera en que funciona el módulo readline
.
Si stdin es un TTY, entonces, cada vez que intenta obtener una línea con input()
, varios bits del módulo de code
, el REPL incorporado, etc., Python llama a cualquier PyOS_InputHook
instalado en lugar de solo leer desde stdin.
Probablemente es más fácil entender lo que hace la línea de readline
: intenta select
en estándar o similar, en bucle para cada nuevo carácter de entrada, o cada 0,1 segundos, o cada señal.
Lo que hace Tkinter
es similar. Es más complicado porque tiene que lidiar con Windows, pero en * nix está haciendo algo muy similar a readline
. Excepto que está llamando a Tcl_DoOneEvent
cada vez que Tcl_DoOneEvent
por el bucle.
Y esa es la clave. Llamar a Tcl_DoOneEvent
repetidamente es exactamente lo mismo que mainloop
.
(Los subprocesos hacen que todo sea más complicado, por supuesto, pero supongamos que no ha creado ningún subproceso en segundo plano. En su código real, si desea crear subprocesos en segundo plano, tendrá un subproceso para todas las cosas de Tkinter
que se bloquean en mainloop
todos modos, ¿verdad?)
Entonces, mientras su código de Python pase la mayor parte del tiempo bloqueado en la entrada de TTY (como suele ocurrir con el intérprete interactivo), el intérprete de Tcl está avanzando y su GUI está respondiendo. Si realiza el bloqueo del intérprete de Python en algo diferente a la entrada TTY, el intérprete Tcl no se está ejecutando y la GUI no responde.
¿Qué pasaría si quisieras hacer lo mismo manualmente en código puro de Python? Tendría que hacerlo si desea, por ejemplo, integrar una GUI Tkinter y un cliente de red basado en select
en una aplicación de un solo hilo, ¿verdad?
Eso es fácil: conducir un bucle desde el otro.
Puede select
con un tiempo de espera de 0.02s (el mismo tiempo de espera que usa el gancho de entrada predeterminado), y llamar a t.dooneevent(Tkinter.DONT_WAIT)
cada vez que t.dooneevent(Tkinter.DONT_WAIT)
por el bucle.
O, alternativamente, puedes dejar que Tk conduzca llamando a mainloop
, pero usa after
y friends para asegurarte de que select
con suficiente frecuencia.
Nota: Esto es más o menos un seguimiento de la pregunta: Tkinter: ¿cuándo necesito llamar a mainloop?
Por lo general, cuando se usa Tkinter , se llama a Tk.mainloop para ejecutar el bucle de eventos y se asegura de que los eventos se procesen correctamente y que las ventanas permanezcan interactivas sin bloqueo.
Cuando se utiliza Tkinter desde un shell interactivo, no parece necesario ejecutar el bucle principal. Tomemos este ejemplo:
>>> import tkinter
>>> t = tkinter.Tk()
Aparecerá una ventana y no se bloqueará: puede interactuar con ella, arrastrarla y cerrarla.
Por lo tanto, algo en el shell interactivo parece reconocer que se creó una ventana y ejecuta el bucle de eventos en segundo plano.
Ahora lo interesante. Vuelva a tomar el ejemplo de arriba, pero luego en el siguiente indicador (sin cerrar la ventana), ingrese cualquier cosa, sin ejecutarlo realmente (es decir, no presione Intro). Por ejemplo:
>>> t = tkinter.Tk()
>>> print(''Not pressing enter now.'') # not executing this
Si ahora intenta interactuar con la ventana de Tk, verá que se bloquea completamente. Así que el bucle de eventos que pensamos que se ejecutaría en segundo plano se detuvo mientras ingresábamos un comando al shell interactivo. Si enviamos el comando ingresado, verá que el bucle de eventos continúa y todo lo que hicimos durante el bloqueo continuará procesándose.
Entonces, la gran pregunta es: ¿Qué es esta magia que ocurre en el shell interactivo? ¿Qué ejecuta el bucle principal cuando no lo estamos haciendo explícitamente? ¿Y por qué necesita detenerse cuando ingresamos comandos (en lugar de detenernos cuando los ejecutamos)?
Nota: Lo anterior funciona de esta manera en el intérprete de línea de comandos, no en IDLE. En cuanto a IDLE, asumo que la GUI no le dirá realmente al intérprete subyacente que se ha ingresado algo, sino que simplemente mantiene la entrada localmente alrededor hasta que se está ejecutando.