web-dev-qa-db-fra.com

Python: attraper la commande Ctrl-C. Demander "vraiment envie de quitter (o / n)", reprendre l'exécution si non

J'ai un programme qui peut avoir une longue exécution. Dans le module principal, j'ai les éléments suivants:

import signal
def run_program()
   ...time consuming execution...

def Exit_gracefully(signal, frame):
    ... log exiting information ...
    ... close any open files ...
    sys.exit(0)

if __name__ == '__main__':
    signal.signal(signal.SIGINT, Exit_gracefully)
    run_program()

Cela fonctionne bien, mais j'aimerais avoir la possibilité de suspendre l'exécution lors de la capture de SIGINT, invitant l'utilisateur s'il souhaite vraiment quitter, et reprenant là où je me suis arrêté dans run_program () s'il décide qu'il ne veut pas quitter.

La seule façon dont je peux penser à le faire est d'exécuter le programme dans un thread séparé, en gardant le thread principal en attente et prêt à attraper SIGINT. Si l'utilisateur veut quitter le thread principal, il peut nettoyer et tuer le thread enfant.

Existe-t-il un moyen plus simple?

32
Colin M

Les gestionnaires de signaux python ne semblent pas être de vrais gestionnaires de signaux; c'est-à-dire qu'ils se produisent après le fait, dans le flux normal et après que le gestionnaire C est déjà revenu. Ainsi, vous essayez de mettre votre logique de sortie dans le gestionnaire de signaux. Comme le gestionnaire de signaux s'exécute dans le thread principal, il bloquera également l'exécution.

Quelque chose comme ça semble bien fonctionner.

import signal
import time
import sys

def run_program():
    while True:
        time.sleep(1)
        print("a")

def exit_gracefully(signum, frame):
    # restore the original signal handler as otherwise evil things will happen
    # in raw_input when CTRL+C is pressed, and our signal handler is not re-entrant
    signal.signal(signal.SIGINT, original_sigint)

    try:
        if raw_input("\nReally quit? (y/n)> ").lower().startswith('y'):
            sys.exit(1)

    except KeyboardInterrupt:
        print("Ok ok, quitting")
        sys.exit(1)

    # restore the exit gracefully handler here    
    signal.signal(signal.SIGINT, exit_gracefully)

if __name__ == '__main__':
    # store the original SIGINT handler
    original_sigint = signal.getsignal(signal.SIGINT)
    signal.signal(signal.SIGINT, exit_gracefully)
    run_program()

Le code restaure le gestionnaire de signal d'origine pour la durée de raw_input; raw_input lui-même n'est pas ré-entrable, et y rentrer entraînera RuntimeError: can't re-enter readline provenir de time.sleep ce que nous ne voulons pas car il est plus difficile à attraper que KeyboardInterrupt. Au lieu de cela, nous avons laissé 2 Ctrl-C consécutifs pour augmenter KeyboardInterrupt.

54
Antti Haapala

de https://Gist.github.com/rtfpessoa/e3b1fe0bbfcd8ac853bf

#!/usr/bin/env python

import signal
import sys

def signal_handler(signal, frame):
  # your code here
  sys.exit(0)

signal.signal(signal.SIGINT, signal_handler)

Au revoir!

0
Marc