Capture keyboardinterrupt en Python sin intentar, excepto

101

¿Hay alguna forma en Python de capturar el KeyboardInterruptevento sin poner todo el código dentro de una declaración try- except?

Quiero salir limpiamente sin dejar rastro si el usuario presiona Ctrl+ C.

Alex
fuente

Respuestas:

149

Sí, puede instalar un manejador de interrupciones usando la señal del módulo y esperar eternamente usando un subproceso .

import signal
import sys
import time
import threading

def signal_handler(signal, frame):
    print('You pressed Ctrl+C!')
    sys.exit(0)

signal.signal(signal.SIGINT, signal_handler)
print('Press Ctrl+C')
forever = threading.Event()
forever.wait()
Johan Kotlinski
fuente
10
Tenga en cuenta que hay algunos problemas específicos de la plataforma con el módulo de señales; no deberían afectar este póster, pero "En Windows, signal () solo se puede llamar con SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV o SIGTERM. A ValueError se planteará en cualquier otro caso ".
bgporter
7
También funciona bien con hilos. Sin while True: continueembargo, espero que nunca lo hagas . (En ese estilo, while True: passsería más ordenado, de todos modos.) Eso sería un desperdicio; intente algo como while True: time.sleep(60 * 60 * 24)(dormir un día a la vez es una cifra completamente arbitraria).
Chris Morgan
1
Si está utilizando la sugerencia de Chris Morgan de usar time(como debería), no olvide import time:)
Seaux
1
Llamar a sys.exit (0) activa una excepción de salida del sistema para mí. Puede hacer que funcione bien si lo usa en combinación con esto: stackoverflow.com/a/13723190/353094
leetNightshade
2
Puede usar signal.pause () en lugar de dormir repetidamente
Croad Langshan
36

Si todo lo que desea es no mostrar el rastreo, haga su código así:

## all your app logic here
def main():
   ## whatever your app does.


if __name__ == "__main__":
   try:
      main()
   except KeyboardInterrupt:
      # do nothing here
      pass

(Sí, sé que esto no responde directamente a la pregunta, pero no está realmente claro por qué es objetable necesitar un bloque try / except; tal vez esto lo haga menos molesto para el OP)

bgporter
fuente
5
Por alguna razón, esto no siempre me funciona. signal.signal( signal.SIGINT, lambda s, f : sys.exit(0))siempre lo hace.
Hal Canary
Esto no siempre funciona con cosas como pygtk que usan hilos. A veces, ^ C simplemente matará el hilo actual en lugar de todo el proceso, por lo que la excepción solo se propagará a través de ese hilo.
Sudo Bash
Hay otra pregunta SO específicamente sobre Ctrl + C con pygtk: stackoverflow.com/questions/16410852/…
bgporter
30

Una alternativa para configurar su propio controlador de señales es usar un administrador de contexto para detectar la excepción e ignorarla:

>>> class CleanExit(object):
...     def __enter__(self):
...             return self
...     def __exit__(self, exc_type, exc_value, exc_tb):
...             if exc_type is KeyboardInterrupt:
...                     return True
...             return exc_type is None
... 
>>> with CleanExit():
...     input()    #just to test it
... 
>>>

Esto elimina el bloque try- exceptconservando alguna mención explícita de lo que está sucediendo.

Esto también le permite ignorar la interrupción solo en algunas partes de su código sin tener que configurar y restablecer nuevamente los controladores de señal cada vez.

Bakuriu
fuente
1
Bien, esta solución parece un poco más directa para expresar el propósito en lugar de tratar con señales.
Seaux
Usando la biblioteca de multiprocesamiento, no estoy seguro de qué objeto debo agregar esos métodos ... ¿alguna pista?
Stéphane
@ Stéphane ¿A qué te refieres? Cuando se trata de multiprocesamiento, tendrá que lidiar con la señal tanto en el proceso principal como en el secundario, ya que podría activarse en ambos. Realmente depende de lo que esté haciendo y cómo se utilizará su software.
Bakuriu
8

Sé que esta es una pregunta antigua, pero vine aquí primero y luego descubrí el atexitmódulo. Todavía no conozco su historial multiplataforma o una lista completa de advertencias, pero hasta ahora es exactamente lo que estaba buscando al tratar de manejar post-KeyboardInterrupt limpieza en Linux. Solo quería incluir otra forma de abordar el problema.

Quiero hacer una limpieza posterior a la salida en el contexto de las operaciones de Fabric, por lo que envolver todo en try/ excepttampoco era una opción para mí. me siento comoatexit puede encajar bien en una situación así, en la que su código no está en el nivel superior de flujo de control.

atexit es muy capaz y legible desde el primer momento, por ejemplo:

import atexit

def goodbye():
    print "You are now leaving the Python sector."

atexit.register(goodbye)

También puede usarlo como decorador (a partir de 2.6; este ejemplo es de los documentos):

import atexit

@atexit.register
def goodbye():
    print "You are now leaving the Python sector."

Si quisiera hacerlo específico para KeyboardInterrupt solo , la respuesta de otra persona a esta pregunta probablemente sea mejor.

Pero tenga en cuenta que el atexitmódulo tiene solo ~ 70 líneas de código y no sería difícil crear una versión similar que trate las excepciones de manera diferente, por ejemplo, pasando las excepciones como argumentos a las funciones de devolución de llamada. (La limitación de atexiteso garantizaría una versión modificada: actualmente no puedo concebir una forma de que las funciones de devolución de llamada de salida conozcan las excepciones;atexit controlador detecta la excepción, llama a su devolución de llamada y luego vuelve a generar esa excepción. Pero podría hacerlo de otra manera).

Para obtener más información, consulte:

atrapasueños
fuente
atexit no funciona para KeyboardInterrupt (Python 3.7)
TimZaman
Trabajó para KeyboardInterrupt aquí (python 3.7, MacOS). ¿Quizás una peculiaridad específica de la plataforma?
Niko Nyman
4

Puede evitar la impresión de un seguimiento de pila para KeyboardInterrupt, sin try: ... except KeyboardInterrupt: pass(la solución más obvia y probablemente "mejor", pero ya la sabe y pidió algo más) reemplazando sys.excepthook. Algo como

def custom_excepthook(type, value, traceback):
    if type is KeyboardInterrupt:
        return # do nothing
    else:
        sys.__excepthook__(type, value, traceback)
Ricardo
fuente
Quiero una salida limpia sin dejar rastro si el usuario presiona ctrl-c
Alex
7
Esto no es cierto en absoluto. La excepción KeyboardInterrupt se crea durante un controlador de interrupciones. El controlador predeterminado para SIGINT genera KeyboardInterrupt, por lo que si no desea ese comportamiento, todo lo que tendría que hacer es proporcionar un controlador de señal diferente para SIGINT. Tiene razón en que las excepciones solo se pueden manejar en un intento / excepto, sin embargo, en este caso, puede evitar que la excepción se plantee en primer lugar.
Matt
1
Sí, lo aprendí unos tres minutos después de la publicación, cuando apareció la respuesta de kotlinski;)
2

Probé las soluciones sugeridas por todos, pero tuve que improvisar el código para que funcionara. A continuación está mi código improvisado:

import signal
import sys
import time

def signal_handler(signal, frame):
    print('You pressed Ctrl+C!')
    print(signal) # Value is 2 for CTRL + C
    print(frame) # Where your execution of program is at moment - the Line Number
    sys.exit(0)

#Assign Handler Function
signal.signal(signal.SIGINT, signal_handler)

# Simple Time Loop of 5 Seconds
secondsCount = 5
print('Press Ctrl+C in next '+str(secondsCount))
timeLoopRun = True 
while timeLoopRun:  
    time.sleep(1)
    if secondsCount < 1:
        timeLoopRun = False
    print('Closing in '+ str(secondsCount)+ ' seconds')
    secondsCount = secondsCount - 1
Rohit Jain
fuente
0

Si alguien está buscando una solución mínima rápida,

import signal

# The code which crashes program on interruption

signal.signal(signal.SIGINT, call_this_function_if_interrupted)

# The code skipped if interrupted
tejasvi88
fuente