Je travaille sur un script python qui démarre plusieurs processus et connexions à une base de données. De temps en temps, je veux tuer le script avec un Ctrl+C signal, 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 est-ce que je fais l'analogue de ceci en Python?
Enregistrez votre gestionnaire avec signal.signal
comme 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é de ici .
Plus de documentation sur signal
peut être trouvée ici .
Vous pouvez le traiter comme une exception (KeyboardInterrupt), comme un autre. Créez un nouveau fichier et lancez-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()
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
Vous pouvez gérer CTRL+C en interceptant l'exception KeyboardInterrupt
. Vous pouvez implémenter n'importe quel code de nettoyage dans le gestionnaire d'exceptions.
De Python's 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
main
renvoyé comme fonction principale et exit_gracefully
comme CTRL + c gestionnaire
if __== '__main__':
try:
main()
except KeyboardInterrupt:
pass
finally:
exit_gracefully()
J'ai adapté le code de @udi pour prendre en charge 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 au clavier (SIGINT
) et la SIGTERM
(kill <process>
)
Vous pouvez utiliser les fonctions du module signal intégré de Python pour configurer des gestionnaires de signaux en python. Plus précisément, la fonction signal.signal(signalnum, handler)
est utilisée pour enregistrer la fonction handler
pour le signal signalnum
.
Contrairement à Matt J sa réponse, j’utilise un objet simple. Cela me donne la possibilité d’analyser ce gestionnaire avec tous les threads devant être arrêtés en toute 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
merci pour les réponses existantes, mais a 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
Personnellement, je ne pouvais pas utiliser try/except KeyboardInterrupt car j'utilisais le mode de socket standard (IPC) qui bloquait. Le SIGINT a donc été créé, mais il n’a été reçu qu’après avoir reçu des données sur le socket.
Définir un gestionnaire de signal se comporte de la même manière.
Par contre, cela ne fonctionne que pour un terminal réel. D'autres environnements de départ pourraient ne pas accepter Ctrl + C ou pré-manipuler le signal.
De plus, il y a des "Exceptions" et des "BaseExceptions" dans Python, qui se différencient par le fait que l'interpréteur doit quitter lui-même proprement, de sorte que certaines exceptions ont une priorité plus élevée que d'autres (Exceptions est dérivée de BaseException)