Comment capturer SIGINT en Python?


536

Je travaille sur un script python qui démarre plusieurs processus et connexions à la base de données. De temps en temps, je veux tuer le script avec un signal Ctrl+ C, et je voudrais faire un peu de nettoyage.

En Perl, je ferais ceci:

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

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

Comment puis-je faire l'analogue de cela en Python?

Réponses:


787

Enregistrez votre gestionnaire avec signal.signalcomme ceci:

#!/usr/bin/env python
import signal
import sys

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

Code adapté d' ici .

Vous signaltrouverez plus de documentation sur ici .  


13
Pourriez-vous me dire pourquoi utiliser ceci au lieu d'une exception KeyboardInterrupt? N'est-ce pas plus intuitif à utiliser?
noio

35
Noio: 2 raisons. Tout d'abord, SIGINT peut être envoyé à votre processus de plusieurs façons (par exemple, 'kill -s INT <pid>'); Je ne sais pas si KeyboardInterruptException est implémenté en tant que gestionnaire SIGINT ou s'il n'attrape vraiment que les pressions Ctrl + C, mais de toute façon, l'utilisation d'un gestionnaire de signal rend votre intention explicite (au moins, si votre intention est la même que celle des OP). Plus important encore, avec un signal, vous n'avez pas à enrouler les prises d'essai autour de tout pour les faire fonctionner, ce qui peut être plus ou moins une gagnante en composabilité et en génie logiciel général selon la structure de votre application.
Matt J

35
Exemple de pourquoi vous voulez intercepter le signal au lieu d'attraper l'exception. Dites que vous exécutez votre programme et de rediriger la sortie vers un fichier journal, ./program.py > output.log. Lorsque vous appuyez sur Ctrl-C, vous souhaitez que votre programme se termine correctement en lui faisant enregistrer que tous les fichiers de données ont été vidés et marqués comme propres pour confirmer qu'ils sont laissés dans un bon état connu. Mais Ctrl-C envoie SIGINT à tous les processus d'un pipeline, donc le shell peut fermer STDOUT (maintenant "output.log") avant que program.py ne termine l'impression du journal final. Python se plaindra, "échec de fermeture dans le destructeur d'objets de fichier: erreur dans sys.excepthook:".
Noah Spurrier

24
Notez que signal.pause () n'est pas disponible sous Windows. docs.python.org/dev/library/signal.html
May Oakes du

10
-1 licornes pour l'utilisation de signal.pause (), suggère que je devrais attendre un tel appel de blocage au lieu de faire un vrai travail. ;)
Nick T

177

Vous pouvez le traiter comme une exception (KeyboardInterrupt), comme n'importe quelle autre. Créez un nouveau fichier et exécutez-le à partir de votre shell avec le contenu suivant pour voir ce que je veux dire:

import time, sys

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

22
Attention lors de l'utilisation de cette solution. Vous devez également utiliser ce code avant le bloc catch KeyboardInterrupt:, signal.signal(signal.SIGINT, signal.default_int_handler)ou vous allez échouer, car KeyboardInterrupt ne se déclenche pas dans toutes les situations où il doit se déclencher! Les détails sont ici .
Velda

67

Et en tant que gestionnaire de contexte:

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

Utiliser:

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

Gestionnaires imbriqués:

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

À partir d'ici: https://gist.github.com/2907502


Il pourrait également lancer un StopIterationpour rompre la boucle la plus intérieure lorsqu'un ctrl-C est pressé, non?
Theo Belaire

@TheoBelaire Au lieu de simplement lancer une StopIteration, je créerais un générateur qui accepte un itérable comme paramètre et enregistre / libère le gestionnaire de signal.
Udi

28

Vous pouvez gérer CTRL+ Cen interceptant l' KeyboardInterruptexception. Vous pouvez implémenter n'importe quel code de nettoyage dans le gestionnaire d'exceptions.


21

De la documentation de Python :

import signal
import time

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

signal.signal(signal.SIGINT, handler)

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

19

Encore un autre extrait

Référé maincomme fonction principale et exit_gracefullycomme gestionnaire CTRL+c

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

4
Vous ne devez utiliser que pour les choses qui ne devraient pas arriver. Dans ce cas, KeyboardInterrupt est censé se produire. Ce n'est donc pas une bonne construction.
Tristan

16
@TristanT Dans toute autre langue oui, mais en Python les exceptions ne sont pas seulement pour des choses qui ne sont pas censées se produire. Il est en fait considéré comme bon style en Python d'utiliser des exceptions pour le contrôle de flux (le cas échéant).
Ian Goldby

8

J'ai adapté le code de @udi pour supporter plusieurs signaux (rien d'extraordinaire):

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

Ce code prend en charge l'appel d'interruption clavier ( SIGINT) et SIGTERM( kill <process>)


5

Contrairement à Matt J, sa réponse, j'utilise un objet simple. Cela me donne la possibilité d'analyser ce gestionnaire pour tous les threads qui doivent être arrêtés en sécurité.

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)

Autre part

while True:
    # task
    if handler.SIGINT:
        break

Vous devez utiliser un événement ou time.sleep()au lieu de faire une boucle occupée sur une variable.
OlivierM

@OlivierM C'est vraiment spécifique à l'application et certainement pas le point de cet exemple. Par exemple, bloquer des appels ou attendre des fonctions ne gardera pas le CPU occupé. De plus, ce n'est qu'un exemple de la façon dont les choses peuvent être faites. Les interruptions clavier sont souvent suffisantes, comme mentionné dans d'autres réponses.
Thomas Devoogdt

4

Vous pouvez utiliser les fonctions du module de signal intégré de Python pour configurer des gestionnaires de signal en python. Plus précisément, la signal.signal(signalnum, handler)fonction est utilisée pour enregistrer la handlerfonction pour le signal signalnum.


3

merci pour les réponses existantes, mais ajouté signal.getsignal()

import signal

# store default handler of signal.SIGINT
default_handler = signal.getsignal(signal.SIGINT)
catch_count = 0

def handler(signum, frame):
    global default_handler, catch_count
    catch_count += 1
    print ('wait:', catch_count)
    if catch_count > 3:
        # recover handler for signal.SIGINT
        signal.signal(signal.SIGINT, default_handler)
        print('expecting KeyboardInterrupt')

signal.signal(signal.SIGINT, handler)
print('Press Ctrl+c here')

while True:
    pass

3

Si vous voulez vous assurer que votre processus de nettoyage se termine, j'ajouterais à la réponse de Matt J en utilisant un SIG_IGN afin que d'autres SIGINTsoient ignorés, ce qui empêchera votre nettoyage d'être interrompu.

import signal
import sys

def signal_handler(signum, frame):
    signal.signal(signum, signal.SIG_IGN) # ignore additional signals
    cleanup() # give your process a chance to clean up
    sys.exit(0)

signal.signal(signal.SIGINT, signal_handler) # register the signal with the signal handler first
do_stuff()

0

Personnellement, je ne pouvais pas utiliser try / except KeyboardInterrupt car j'utilisais le mode socket standard (IPC) qui bloque. Le SIGINT a donc été mis en file d'attente, mais n'est venu qu'après avoir reçu des données sur le socket.

La définition d'un gestionnaire de signaux se comporte de la même manière.

En revanche, cela ne fonctionne que pour un terminal réel. D'autres environnements de démarrage peuvent ne pas accepter Ctrl+ Cou pré-gérer le signal.

De plus, il existe des "Exceptions" et "BaseExceptions" en Python, qui diffèrent dans le sens où l'interpréteur doit se fermer proprement, donc certaines exceptions ont une priorité plus élevée que d'autres (les exceptions sont dérivées de BaseException)

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.