¿No puede detectar KeyboardInterrupt en el símbolo del sistema dos veces?

Hoy, tuve que comprobar cómo se ejecuta mi script en el símbolo del sistema de Windows [1] , cuando noté algo extraño. Estaba trabajando en algo similar a esto, pero esto es suficiente para demostrar el problema. Aquí está el código.

def bing(): try: raw_input() except KeyboardInterrupt: print 'This is what actually happened here!' try: # pardon me for those weird strings bing() # as it's consistent with everything in the chat room (see below) print 'Yoo hoo...' except KeyboardInterrupt: print 'Nothing happens here too!' 

Aquí está la situación. Cuando se ejecuta la secuencia de comandos, espera la entrada y se supone que el usuario debe presionar Ctrl + C para activar un KeyboardInterrupt que debería (debería) ser atrapado por el bloque de except dentro de bing() . Por lo tanto, esta debe ser la salida real. Y esto es lo que sucede cuando lo ejecuto en mi terminal de Ubuntu e IDLE (tanto en Windows como en Ubuntu).

 This is what actually happened here! Yoo hoo... 

Pero, esto no sale como se esperaba en el símbolo del sistema de Windows. Prefiero obtener una salida extraña.

 This is what actually happened here! Nothing happens here too! 

Parece que un solo KeyboardInterrupt propaga a lo largo del progtwig y finalmente lo termina.

Intenté todo lo que pude hacer. Primero, utilicé una signal.signal para manejar el SIGINT (que no funcionó), y luego usé la función de manejo para generar una Exception que luego detecté (que tampoco funcionó), y luego las cosas se pusieron más Complicado de lo que solía ser. Entonces, aterricé de regreso a mi viejo try... catch . Luego, fui a la sala para los pitones.

@poke sugirió que se EOFError un EOFError cuando presionamos Ctrl + C. Luego, @ZeroPiraeus dijo que EOFError se EOFError cuando uno presiona Ctrl + Z y Enter .

Eso fue útil, lo que impulsó la discusión después de unos minutos de juguetear . ¡Pronto, todo se convirtió en un caos! ¡Algunos resultados fueron buenos, otros inesperados y algunos se volvieron locos!

¡Bicho raro!

La conclusión fue dejar de usar Windows y pedir a mis amigos que usen la Terminal (estoy de acuerdo). Sin embargo, podría hacer una solución al capturar el EOFError junto con el EOFError KeyboardInterrupt . Si bien se siente flojo presionar Ctrl + Z e Ingresar cada vez, no es un gran problema para mí. Pero, esto es una obsesión para mí.

En una investigación adicional, también noté que no hay un KeyboardInterrupt en el CMD cuando presiono Ctrl + C.

Qaa ???

No hay nada en el fondo. Entonces, ¿qué pasa aquí de todos modos? ¿Por qué se propaga el KeyboardInterrupt ? ¿Hay alguna forma (en absoluto) de hacer que la salida sea consistente con el terminal?


[1]: Siempre he trabajado en la terminal, pero hoy necesitaba asegurarme de que mi script funciona en todas las plataformas (especialmente porque la mayoría de mis amigos no son progtwigdores y solo se apegan a Windows).

La pregunta user2357112 vinculada, explica esto de alguna manera: ¿Por qué no puedo manejar un KeyboardInterrupt en python? .

La interrupción del teclado se genera de forma asíncrona, por lo que no finaliza inmediatamente la aplicación. En su lugar, Ctrl + C se maneja en algún tipo de bucle de eventos que tarda un poco en llegar. Desafortunadamente, esto significa que no puede capturar de forma confiable el KeyboardInterrupt de KeyboardInterrupt en este caso. Pero podemos hacer algunas cosas para llegar allí.

Como expliqué ayer, la excepción que detiene la llamada raw_input no es el KeyboardInterrupt sino un EOFError . Puedes verificar esto fácilmente cambiando tu función de bing esta manera:

 def bing(): try: raw_input() except Exception as e: print(type(e)) 

Verá que el tipo de excepción que se imprime es EOFError y no KeyboardInterrupt . También verá que la print ni siquiera se realizó completamente: no hay una nueva línea. Aparentemente, esto se debe a que la interrupción que llegó justo después de que la statement de impresión escribiera el tipo de excepción en la salida estándar se interrumpió la salida. También puede ver esto cuando agrega un poco más de material a la impresión:

 def bing(): try: raw_input() except EOFError as e: print 'Exception raised:', 'EOF Error' 

Tenga en cuenta que estoy usando dos argumentos separados aquí para la statement de impresión. Cuando ejecutamos esto, podemos ver el texto “Excepción provocada”, pero el “Error EOF” no aparecerá. En su lugar, se activará la except desde la llamada externa y se detectará la interrupción del teclado.

Aunque las cosas se ponen un poco más fuera de control en Python 3. Toma este código:

 def bing(): try: input() except Exception as e: print('Exception raised:', type(e)) try: bing() print('After bing') except KeyboardInterrupt: print('Final KeyboardInterrupt') 

Esto es casi exactamente lo que hicimos antes, solo enmendado para la syntax de Python 3. Si ejecuto esto, obtengo el siguiente resultado:

 Exception raised:  After bing Final KeyboardInterrupt 

Así que podemos ver nuevamente que el EOFError está correctamente capturado, pero por alguna razón, Python 3 continúa la ejecución mucho más tiempo que Python 2 aquí, ya que la impresión después de bing() se ejecuta. Lo que es peor, en algunas ejecuciones con cmd.exe, obtengo el resultado de que no se detecta ninguna interrupción del teclado (por lo que aparentemente, la interrupción se procesó después de que el progtwig ya se había completado).

Entonces, ¿qué podemos hacer al respecto si queremos asegurarnos de que recibimos una interrupción del teclado? Una cosa que sabemos con certeza es que al interrumpir un raw_input() input() (o raw_input() ) siempre se genera un EOFError : Eso es lo único que hemos visto todo el tiempo. Entonces, lo que podemos hacer es atrapar eso y luego asegurarnos de que recibimos la interrupción del teclado.

Una forma de hacerlo sería simplemente elevar un KeyboardInterrupt desde el controlador de excepciones para EOFError . Pero esto no solo se siente un poco sucio, sino que tampoco garantiza que una interrupción sea en realidad lo que terminó la solicitud de entrada en primer lugar (¿quién sabe qué más puede provocar un error EOFError?). Así que deberíamos tener la señal de interrupción ya existente para generar la excepción.

La forma en que hacemos esto es bastante simple: esperamos. Hasta ahora, nuestro problema era que la ejecución continuaba porque la excepción no llegó lo suficientemente rápido. Entonces, ¿qué pasa si esperamos un poco para que la excepción llegue antes de continuar con otras cosas?

 import time def bing(): try: input() # or raw_input() for Python 2 except EOFError: time.sleep(1) try: bing() print('After bing') except KeyboardInterrupt: print('Final KeyboardInterrupt') 

Ahora, simplemente capturamos el error EOFError y esperamos un poco para permitir que los procesos asíncronos en la parte posterior se resuelvan y decidan si interrumpir la ejecución o no. Esto siempre me permite capturar el KeyboardInterrupt de KeyboardInterrupt en la prueba / captura externa y no imprimirá nada más, excepto lo que hago en el controlador de excepciones.

Puede que te preocupe que un segundo sea mucho tiempo para esperar, pero en nuestros casos, donde interrumpimos la ejecución, ese segundo nunca dura mucho. Solo unos pocos milisegundos después del tiempo. time.sleep , la interrupción se time.sleep y estamos en nuestro controlador de excepciones. Por lo tanto, el segundo es solo un dispositivo a prueba de fallos que esperará el tiempo suficiente para que la excepción llegue a tiempo. ¿Y en el peor de los casos, cuando en realidad no hay una interrupción sino solo un EOFError “normal”? Luego, el progtwig que anteriormente estaba bloqueando infinitamente para la entrada del usuario tardará un segundo más en continuar; eso realmente no debería ser un problema nunca (sin mencionar que el EOFError es probablemente muy raro).

Así que ahí tenemos nuestra solución: solo atrapa el EOFError, y espera un poco. Al menos espero que esta sea una solución que funcione en otras máquinas que no sean las mías ^ _ ^ “Después de anoche, no estoy muy seguro de esto, pero al menos obtuve una experiencia consistente en todos mis terminales y diferentes versiones de Python .