2009-07-10 16 views
393

Estoy trabajando en un script de python que inicia varios procesos y conexiones de bases de datos. De vez en cuando quiero matar la secuencia de comandos con una señal Ctrl + C, y me gustaría hacer un poco de limpieza.¿Cómo capturo SIGINT en Python?

En Perl que me gustaría hacer esto:

$SIG{'INT'} = 'exit_gracefully'; 

sub exit_gracefully { 
    print "Caught ^C \n"; 
    exit (0); 
} 

¿Cómo se hace el análogo de esto en Python?

+3

Asegúrese de ver: http://blip.tv/file/2232410 (Python GIL Mindblowing) antes de las discusiones y las señales de mezcla (si tuviera no lo ha hecho, por supuesto) . Las diapositivas están aquí: http://www.dabeaz.com/python/GIL.pdf. –

Respuesta

561

registrar el controlador con signal.signal así:

#!/usr/bin/env python 
import signal 
import sys 
def signal_handler(signal, frame): 
     print('You pressed Ctrl+C!') 
     sys.exit(0) 
signal.signal(signal.SIGINT, signal_handler) 
print('Press Ctrl+C') 
signal.pause() 

Código adaptado de here.

Más documentación en signal se puede encontrar here.

+0

-1 para bucle infinito. Consulte la respuesta de Chad a continuación sobre signal.pause() y actualice. – Shabbyrobe

+60

No está claro por qué no está bien hacer un ciclo infinito en el código de demostración, pero actualizado de todos modos. –

+8

¿Podría decirme por qué usar esto en lugar de una excepción KeyboardInterrupt? ¿No es más intuitivo de usar? – noio

21

que puede manejar CTRL +C por la captura de la excepción KeyboardInterrupt. Puede implementar cualquier código de limpieza en el manejador de excepciones.

136

Puede tratarlo como una excepción (KeyboardInterrupt), como cualquier otra. Hacer un nuevo archivo y ejecutarlo desde su shell con el siguiente contenido para ver lo que quiero decir:

import time, sys 

x = 1 
while True: 
    try: 
     print x 
     time.sleep(.3) 
     x += 1 
    except KeyboardInterrupt: 
     print "Bye" 
     sys.exit() 
+12

Atención al usar esta solución. También debe usar este código antes del bloque de captura KeyboardInterrupt: 'signal.signal (signal.SIGINT, signal.default_int_handler)', o va a fallar, porque KeyboardInterrupt no se dispara en todas las situaciones en las que debería disparar. Los detalles son [aquí] (http://stackoverflow.com/a/40785230/1908192). – Velda

4

Usted puede utilizar las funciones de Python incorporada signal module para configurar los gestores de señales en Python. Específicamente, la función signal.signal(signalnum, handler) se usa para registrar la función handler para la señal signalnum.

17

De Pitón de documentation:

import signal 
import time 

def handler(signum, frame): 
    print 'Here you go' 

signal.signal(signal.SIGINT, handler) 

time.sleep(10) # Press Ctrl+c here 
49

Y como un gestor de contexto:

import signal 

class GracefulInterruptHandler(object): 

    def __init__(self, sig=signal.SIGINT): 
     self.sig = sig 

    def __enter__(self): 

     self.interrupted = False 
     self.released = False 

     self.original_handler = signal.getsignal(self.sig) 

     def handler(signum, frame): 
      self.release() 
      self.interrupted = True 

     signal.signal(self.sig, handler) 

     return self 

    def __exit__(self, type, value, tb): 
     self.release() 

    def release(self): 

     if self.released: 
      return False 

     signal.signal(self.sig, self.original_handler) 

     self.released = True 

     return True 

de usar:

with GracefulInterruptHandler() as h: 
    for i in xrange(1000): 
     print "..." 
     time.sleep(1) 
     if h.interrupted: 
      print "interrupted!" 
      time.sleep(2) 
      break 

manejadores anidados:

with GracefulInterruptHandler() as h1: 
    while True: 
     print "(1)..." 
     time.sleep(1) 
     with GracefulInterruptHandler() as h2: 
      while True: 
       print "\t(2)..." 
       time.sleep(1) 
       if h2.interrupted: 
        print "\t(2) interrupted!" 
        time.sleep(2) 
        break 
     if h1.interrupted: 
      print "(1) interrupted!" 
      time.sleep(2) 
      break 

A partir de aquí: https://gist.github.com/2907502

+1

He añadido mi propio sabor https://gist.github.com/camilin87/8571791 –

+0

También podría arrojar un 'StopIteration' para romper el bucle más interno cuando se presiona un Ctrl-C, ¿verdad? –

+0

@ TheoBelaire En lugar de simplemente lanzar una StopIteration, crearía un generador que acepta un iterable como parámetro y registra/libera el manejador de señal. – Udi

9

Sin embargo, otro fragmento

Referido main como la función principal y exit_gracefully como el CTRL +c manejador

if __name__ == '__main__': 
    try: 
     main() 
    except KeyboardInterrupt: 
     pass 
    finally: 
     exit_gracefully() 
+3

Deberías usarlo solo excepto por cosas que no se supone que sucedan. En este caso, se supone que ocurra KeyboardInterrupt. Entonces esta no es una buena construcción. –

+4

@TristanT En cualquier otro idioma, sí, pero en Python las excepciones no son solo para cosas que no deberían suceder. En realidad, se considera que un buen estilo en Python es usar excepciones para el control de flujo (cuando corresponda). –

5

adapté el código de @udi para admitir señales múltiples (nada sofisticado):

class GracefulInterruptHandler(object): 
    def __init__(self, signals=(signal.SIGINT, signal.SIGTERM)): 
     self.signals = signals 
     self.original_handlers = {} 

    def __enter__(self): 
     self.interrupted = False 
     self.released = False 

     for sig in self.signals: 
      self.original_handlers[sig] = signal.getsignal(sig) 
      signal.signal(sig, self.handler) 

     return self 

    def handler(self, signum, frame): 
     self.release() 
     self.interrupted = True 

    def __exit__(self, type, value, tb): 
     self.release() 

    def release(self): 
     if self.released: 
      return False 

     for sig in self.signals: 
      signal.signal(sig, self.original_handlers[sig]) 

     self.released = True 
     return True 

Este código de soporte de la llamada interrupción de teclado (SIGINT) y el SIGTERM (kill <process>)

2

En contraste con Matt J su respuesta, yo uso un simple objeto. Esto me da la posibilidad de analizar este manejador a todos los hilos que necesitan ser bloqueados.

class SIGINT_handler(): 
    def __init__(self): 
     self.SIGINT = False 

    def signal_handler(self, signal, frame): 
     print('You pressed Ctrl+C!') 
     self.SIGINT = True 


handler = SIGINT_handler() 
signal.signal(signal.SIGINT, handler.signal_handler) 

otra parte

while True: 
    # task 
    if handler.SIGINT: 
     break 
Cuestiones relacionadas