¿Por qué no puedo manejar un KeyboardInterrupt en Python?
windows (6)
Estoy escribiendo código de pitón 2.6.6 en Windows que se ve así:
try:
dostuff()
except KeyboardInterrupt:
print "Interrupted!"
except:
print "Some other exception?"
finally:
print "cleaning up...."
print "done."
dostuff()
es una función que se repite para siempre, leyendo una línea a la vez desde una secuencia de entrada y actuando sobre ella. Quiero poder detenerlo y limpiar cuando presiono ctrl-c.
Lo que está sucediendo en cambio es que el código debajo except KeyboardInterrupt:
no se está ejecutando en absoluto. Lo único que se imprime es "limpiar ...", y luego se imprime una traza que se ve así:
Traceback (most recent call last):
File "filename.py", line 119, in <module>
print ''cleaning up...''
KeyboardInterrupt
Entonces, el código de manejo de excepciones NO se está ejecutando, y el rastreo afirma que se produjo un KeyboardInterrupt durante la cláusula finally , lo que no tiene sentido porque presionar ctrl-c es lo que provocó que esa parte se ejecutara en primer lugar. Incluso la cláusula genérica except:
no se está ejecutando.
EDITAR: En base a los comentarios, reemplacé el contenido del bloque try:
con sys.stdin.read (). El problema todavía ocurre exactamente como se describe, con la primera línea del bloque finally:
running y luego imprimiendo el mismo traceback.
EDIT # 2: si agrego casi cualquier cosa después de la lectura, el controlador funciona. Entonces, esto falla:
try:
sys.stdin.read()
except KeyboardInterrupt:
...
Pero esto funciona:
try:
sys.stdin.read()
print "Done reading."
except KeyboardInterrupt:
...
Esto es lo que está impreso:
Done reading. Interrupted!
cleaning up...
done.
Entonces, por alguna razón, la "lectura hecha". línea se imprime, aunque la excepción se produjo en la línea anterior. Eso no es realmente un problema, obviamente tengo que ser capaz de manejar una excepción en cualquier lugar dentro del bloque "try". Sin embargo, la impresión no funciona normalmente, ¡no imprime una nueva línea como se supone que debe hacerlo! El "Interruped" está impreso en la misma línea ... ¿con un espacio delante de él, por alguna razón ...? De todos modos, después de eso, el código hace lo que se supone que debe hacer.
Me parece que esto es un error al manejar una interrupción durante una llamada al sistema bloqueada.
Aquí hay una conjetura sobre lo que está pasando:
- al presionar Ctrl-C se rompe la declaración de "impresión" (por alguna razón ... ¿error? ¿Limitación de Win32?)
- al presionar Ctrl-C también se lanza el primer KeyboardInterrupt, en dostuff ()
- El manejador de excepciones se ejecuta e intenta imprimir "Interrumpido", pero la instrucción "imprimir" se rompe y arroja otro KeyboardInterrupt.
- La cláusula finally se ejecuta e intenta imprimir "limpiar ...", pero la instrucción "imprimir" se rompe y arroja otro KeyboardInterrupt.
El manejo asíncrono de excepciones lamentablemente no es confiable (excepciones planteadas por manejadores de señal, contextos externos a través de C API, etc.). Puede aumentar sus posibilidades de manejar la excepción asíncrona correctamente si hay alguna coordinación en el código acerca de qué parte del código es responsable de atraparlos (lo más alto posible en la pila de llamadas parece apropiado, excepto para funciones muy críticas).
La función llamada ( dostuff
) o funciones más abajo en la pila pueden tener un inconveniente para KeyboardInterrupt o BaseException que usted no pudo / no pudo explicar.
Este caso trivial funcionó bien con Python 2.6.6 (x64) interactivo + Windows 7 (64 bits):
>>> import time
>>> def foo():
... try:
... time.sleep(100)
... except KeyboardInterrupt:
... print "INTERRUPTED!"
...
>>> foo()
INTERRUPTED! #after pressing ctrl+c
EDITAR:
Tras una investigación adicional, probé lo que creo que es el ejemplo que otros han usado para reproducir el problema. Yo era flojo, así que dejé fuera el "finalmente"
>>> def foo():
... try:
... sys.stdin.read()
... except KeyboardInterrupt:
... print "BLAH"
...
>>> foo()
Esto regresa inmediatamente después de presionar CTRL + C. Lo interesante sucedió cuando intenté llamar a foo de nuevo:
>>> foo()
Traceback (most recent call last):
File "c:/Python26/lib/encodings/cp437.py", line 14, in decode
def decode(self,input,errors=''strict''):
KeyboardInterrupt
La excepción se planteó inmediatamente sin que presioné CTRL + C.
Esto parece tener sentido: parece que estamos lidiando con los matices de cómo se manejan las excepciones asincrónicas en Python. Puede tomar varias instrucciones de bytecode antes de que la excepción asincrónica realmente aparezca y luego se lance dentro del contexto de ejecución actual. (Ese es el comportamiento que he visto cuando jugaba con él en el pasado)
Consulte la API de C: http://docs.python.org/c-api/init.html#PyThreadState_SetAsyncExc
Así que esto explica de alguna manera por qué KeyboardInterrupt se plantea en el contexto de la ejecución de la declaración finally en este ejemplo:
>>> def foo():
... try:
... sys.stdin.read()
... except KeyboardInterrupt:
... print "interrupt"
... finally:
... print "FINALLY"
...
>>> foo()
FINALLY
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in foo
KeyboardInterrupt
Podría haber una mezcla loca de manejadores de señal personalizados mezclados con el controlador KeyboardInterrupt / CTRL + C estándar del intérprete que está dando como resultado este tipo de comportamiento. Por ejemplo, la llamada de lectura () ve la señal y las fianzas, pero vuelve a subir la señal después de anular el registro de su manejador. No lo sabría con seguridad sin inspeccionar la base de código del intérprete.
Esta es la razón por la cual generalmente evito hacer uso de excepciones asíncronas ...
EDIT 2
Creo que hay un buen caso para un informe de error.
Nuevamente, más teorías ... (solo basadas en la lectura del código) Consulte la fuente del objeto del archivo: http://svn.python.org/view/python/branches/release26-maint/Objects/fileobject.c?revision=81277&view=markup
file_read llama a Py_UniversalNewlineFread (). fread puede regresar con un error con errno = EINTR (realiza su propia gestión de señal). En este caso, Py_UniversalNewlineFread () se bloquea pero no realiza ninguna comprobación de señal con PyErr_CheckSignals () para que los manejadores puedan recibir llamadas de forma sincrónica. file_read borra el error de archivo pero tampoco llama a PyErr_CheckSignals ().
Consulte getline () y getline_via_fgets () para ver ejemplos de cómo se usa. El patrón está documentado en este informe de error para un problema similar: ( http://bugs.python.org/issue1195 ). Entonces parece que la señal es manejada en un tiempo indeterminado por el intérprete.
Supongo que hay poco valor en el buceo más profundo ya que aún no está claro si el ejemplo sys.stdin.read () es un análogo apropiado de la función "dostuff ()". (podría haber múltiples errores en juego)
Esto funciona para mí:
import sys
if __name__ == "__main__":
try:
sys.stdin.read()
print "Here"
except KeyboardInterrupt:
print "Worked"
except:
print "Something else"
finally:
print "Finally"
Intente poner una línea fuera de su función dostuff () o mueva la condición de bucle fuera de la función. Por ejemplo:
try:
while True:
dostuff()
except KeyboardInterrupt:
print "Interrupted!"
except:
print "Some other exception?"
finally:
print "cleaning up...."
print "done."
Tener un problema similar y esta es mi solución:
try:
some_blocking_io_here() # CTRL-C to interrupt
except:
try:
print() # any i/o will get the second KeyboardInterrupt here?
except:
real_handler_here()
sys.stdin.read()
es una llamada al sistema, por lo que el comportamiento será diferente para cada sistema. Para Windows 7, creo que lo que está sucediendo es que la entrada se está almacenando en el búfer y, por lo tanto, está llegando a donde sys.stdin.read()
está devolviendo todo a la Ctrl-C y tan pronto como acceda a sys.stdin nuevamente '' Enviaré la "Ctrl-C".
prueba lo siguiente,
def foo():
try:
print sys.stdin.read()
print sys.stdin.closed
except KeyboardInterrupt:
print "Interrupted!"
Esto sugiere que hay un almacenamiento en búfer de stdin que está causando que otra llamada a stdin reconozca la entrada del teclado
def foo():
try:
x=0
while 1:
x += 1
print x
except KeyboardInterrupt:
print "Interrupted!"
no parece haber un problema
¿Está dostuff()
leyendo de stdin?
def foo():
try:
x=0
while 1:
x+=1
print (x)
except KeyboardInterrupt:
print ("interrupted!!")
foo()
Eso funciona bien