120 votes

Capturer leclavier Interruption en Python sans essayer sauf

Y a-t-il un moyen en Python de capturer l'événement KeyboardInterrupt sans mettre tout le code à l'intérieur d'une instruction try-except ?

Je veux sortir proprement sans laisser de trace si l'utilisateur appuie sur <strong>Ctrl</strong>+<strong>C</strong>.

172voto

kotlinski Points 12815

Oui, vous pouvez installer un gestionnaire d'interruption en utilisant le module signal, et attendre indéfiniment en utilisant un threading.Event:

import signal
import sys
import time
import threading

def signal_handler(signal, frame):
    print('Vous avez appuyé sur Ctrl+C!')
    sys.exit(0)

signal.signal(signal.SIGINT, signal_handler)
print('Appuyez sur Ctrl+C')
forever = threading.Event()
forever.wait()

11 votes

Notez qu'il y a quelques problèmes spécifiques à la plate-forme avec le module de signal -- ne devrait pas affecter ce message, mais "Sous Windows, signal() ne peut être appelé qu'avec SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, ou SIGTERM. Une ValueError sera soulevée dans tout autre cas."

7 votes

Fonctionne bien aussi avec les threads. J'espère que vous ne ferez jamais while True: continue, cependant. (Dans ce style, while True: pass serait plus propre, de toute façon.) Ce serait très gaspilleur; essayez quelque chose comme while True: time.sleep(60 * 60 * 24) (dormir pendant un jour à la fois est un chiffre totalement arbitraire).

1 votes

Si vous utilisez la suggestion de Chris Morgan d'utiliser time (comme vous devriez), n'oubliez pas d' importer le temps :)

43voto

bgporter Points 11119

Si tout ce que vous voulez est de ne pas afficher la trace de la pile, modifiez votre code comme ceci :

## toute votre logique d'application ici
def main():
   ## quoi que fasse votre application.

if __name__ == "__main__":
   try:
      main()
   except KeyboardInterrupt:
      # ne rien faire ici
      pass

(Oui, je sais que cela ne répond pas directement à la question, mais il n'est pas vraiment clair pourquoi avoir besoin d'un bloc try/except est objectionnable - peut-être que cela rend cela moins ennuyeux pour l'OP)

6 votes

Pour une raison quelconque, cela ne fonctionne pas toujours pour moi. signal.signal( signal.SIGINT, lambda s, f : sys.exit(0)) fonctionne toujours.

0 votes

Cela ne fonctionne pas toujours avec des choses telles que pygtk qui utilisent des threads. Parfois, ^C tuera simplement le thread courant au lieu de l'ensemble du processus, de sorte que l'exception ne se propagera que dans ce thread.

0 votes

Il y a une autre question sur SO spécifiquement sur Ctrl+C avec pygtk: stackoverflow.com/questions/16410852/…

33voto

Bakuriu Points 22607

Une alternative à la définition de votre propre gestionnaire de signaux est d'utiliser un gestionnaire de contexte pour intercepter l'exception et l'ignorer :

>>> 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
... 
>>>

Cela supprime le bloc try-except tout en conservant une mention explicite de ce qui se passe.

Cela vous permet également d'ignorer l'interruption uniquement dans certaines parties de votre code sans avoir à définir à nouveau les gestionnaires de signaux à chaque fois.

2 votes

Bien, cette solution semble en effet être un peu plus directe pour exprimer l'objectif plutôt que de traiter avec des signaux.

0 votes

En utilisant la bibliothèque multiprocessing, je ne suis pas sûr sur quel objet je devrais ajouter ces méthodes .. une idée ?

0 votes

@Stéphane Que voulez-vous dire? Lorsque vous traitez avec le multiprocessus, vous devrez gérer le signal à la fois dans les processus parent et enfant, car il pourrait être déclenché dans les deux cas. Cela dépend vraiment de ce que vous faites et de la façon dont votre logiciel sera utilisé.

9voto

seafangs Points 509

Je sais que c'est une vieille question mais je suis venu ici d'abord et puis j'ai découvert le module atexit. Je ne connais pas encore son historique multiplateforme ou une liste complète des avertissements, mais jusqu'à présent, c'est exactement ce que je cherchais pour essayer de gérer le nettoyage après une interruption de clavier sur Linux. Je voulais juste proposer une autre façon d'aborder le problème.

Je veux effectuer un nettoyage post-sortie dans le contexte des opérations de Fabric, donc envelopper tout dans un bloc try/except n'était pas une option pour moi non plus. J'ai l'impression que atexit peut convenir dans une telle situation, où votre code n'est pas au niveau supérieur du flux de contrôle.

atexit est très capable et lisible tel quel, par exemple:

import atexit

def goodbye():
    print "Vous quittez désormais le secteur Python."

atexit.register(goodbye)

Vous pouvez également l'utiliser comme un décorateur (à partir de la version 2.6; cet exemple provient de la documentation):

import atexit

@atexit.register
def goodbye():
    print "Vous quittez désormais le secteur Python."

Si vous vouliez le rendre spécifique à KeyboardInterrupt uniquement, la réponse d'une autre personne à cette question est probablement meilleure.

Mais notez que le module atexit ne comporte que ~70 lignes de code et il ne serait pas difficile de créer une version similaire qui traite les exceptions différemment, par exemple en passant les exceptions en tant qu'arguments aux fonctions de rappel. (La limitation de atexit qui justifierait une version modifiée: actuellement je ne peux pas concevoir de moyen pour les fonctions de rappel de sortie de savoir sur les exceptions; le gestionnaire atexit attrape l'exception, appelle votre ou vos fonctions de rappel, puis relève cette exception. Mais vous pourriez le faire autrement.)

Pour plus d'informations, voir:

2 votes

Atexit ne fonctionne pas pour KeyboardInterrupt (python 3.7)

0 votes

A travaillé pour KeyboardInterrupt ici (python 3.7, MacOS). Peut-être une bizarrerie spécifique à la plate-forme?

0 votes

Peut confirmer que atexit fonctionne à la fois sur MacOS et Ubuntu 18.04 pour python 3.7 et 3.8

4voto

delnan Points 52260

Vous pouvez empêcher l'impression d'une trace de pile pour KeyboardInterrupt, sans try: ... except KeyboardInterrupt: pass (la solution la plus évidente et probablement la "meilleure", mais vous le savez déjà et avez demandé autre chose) en remplaçant sys.excepthook. Quelque chose comme

def custom_excepthook(type, value, traceback):
    if type is KeyboardInterrupt:
        return # ne rien faire
    else:
        sys.__excepthook__(type, value, traceback)

0 votes

Je veux une sortie propre sans trace si l'utilisateur appuie sur ctrl-c

7 votes

Ceci n'est pas du tout vrai. L'exception KeyboardInterrupt est créée lors d'un gestionnaire d'interruption. Le gestionnaire par défaut pour SIGINT lève l'exception KeyboardInterrupt donc si vous ne voulez pas ce comportement, vous aurez juste à fournir un gestionnaire de signal différent pour SIGINT. Vous avez raison que les exceptions ne peuvent être gérées que dans un bloc try/except mais dans ce cas, vous pouvez empêcher l'exception d'être jamais levée en premier lieu.

1 votes

Oui, j'ai appris cela environ trois minutes après avoir posté, lorsque la réponse de kotlinski est arrivée ;)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X