Catching KeyboardInterrupt in Python während des Herunterfahrens des Programms

77

Ich schreibe ein Befehlszeilenprogramm in Python, das, da es sich um Produktionscode handelt, sauber heruntergefahren werden sollte, ohne eine Reihe von Dingen (Fehlercodes, Stapelspuren usw.) auf den Bildschirm zu werfen. Dies bedeutet, dass ich Tastaturinterrupts abfangen muss.

Ich habe versucht, beide einen try catch-Block zu verwenden, wie:

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        print 'Interrupted'
        sys.exit(0)

und das Signal selbst abfangen (wie in diesem Beitrag ):

import signal
import sys

def sigint_handler(signal, frame):
    print 'Interrupted'
    sys.exit(0)
signal.signal(signal.SIGINT, sigint_handler)

Beide Methoden scheinen im normalen Betrieb recht gut zu funktionieren. Wenn der Interrupt jedoch während des Bereinigungscodes am Ende der Anwendung auftritt, scheint Python immer etwas auf dem Bildschirm zu drucken. Das Fangen des Interrupts gibt

^CInterrupted
Exception KeyboardInterrupt in <bound method MyClass.__del__ of <path.to.MyClass object at 0x802852b90>> ignored

wohingegen die Handhabung des Signals entweder ergibt

^CInterrupted
Exception SystemExit: 0 in <Finalize object, dead> ignored

oder

^CInterrupted
Exception SystemExit: 0 in <bound method MyClass.__del__ of <path.to.MyClass object at 0x802854a90>> ignored

Diese Fehler sind nicht nur hässlich, sie sind auch nicht sehr hilfreich (insbesondere für Endbenutzer ohne Quellcode)!

Der Bereinigungscode für diese Anwendung ist ziemlich groß, daher besteht eine gute Chance, dass dieses Problem von echten Benutzern auftritt. Gibt es eine Möglichkeit, diese Ausgabe abzufangen oder zu blockieren, oder muss ich mich nur damit befassen?

Dan
quelle
2
Warum ersetzen Sie nicht sys.stdout/ sys.stderr? Wie sys.stderr = open(os.devnull, 'w')? Wenn Sie sich wirklich nicht für die endgültige Ausgabe interessieren, scheint dies die offensichtliche Lösung zu sein.
Bakuriu
1
Es gibt eine, os._exitaber es sieht für mich wie Nasendämonen aus. Wo ist Ihr Bereinigungscode ? Verwenden Sie dafür das atexit- Modul?
wim
1
@ Bakuriu: Während das Umleiten von stderr die Ausgabe leiser macht, werden auch legitime Fehler unterdrückt, gegen die der Benutzer etwas unternehmen kann, z. B. Datei nicht gefunden oder Host nicht erreichbar.
Dan
4
@ Dan Es muss nicht sein /dev/null. Sie können ein benutzerdefiniertes dateiähnliches Objekt schreiben, das nur Nachrichten mit einem bestimmten Format verbirgt.
Bakuriu
2
@ Bakuriu: Kommt mir immer noch ziemlich hackig vor. Ich werde das tun, wenn ich muss, aber ich denke, das sollte in die Sprache eingebaut werden.
Dan

Antworten:

110

In diesem Thread finden Sie einige nützliche Informationen zum Beenden und zu Tracebacks.

Wenn Sie mehr daran interessiert sind, das Programm einfach zu beenden, versuchen Sie etwas wie dieses (dies wird auch die Beine unter dem Bereinigungscode herausnehmen):

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        print('Interrupted')
        try:
            sys.exit(0)
        except SystemExit:
            os._exit(0)
Dan Hogan
quelle
5
Ich würde empfehlen as, den Exit-Code auszunehmen , dann zu greifen und wiederzuverwenden.
wizzwizz4
5
Ja. Man sollte definitiv nicht mit 0auf KeyboardInterrupt beenden . Wenn jemand Ihr Skript in einem Skript, einer Pipe oder was auch immer verwendet, wird er denken, dass Ihr Code normal ausgeführt wird.
user3342816
4
Linux wird normalerweise mit 130: 128 + 2 beendet. Tldp.org/LDP/abs/html/exitcodes.html#EXITCODESREF . Es kann keine plattformübergreifende Exit-Code-Funktion auf Python gefunden werden. Aber 1ist definitiv besser als0
user3342816
Ich bin nicht anderer Meinung als ein besserer Exit-Code. Der bereitgestellte Code war jedoch eine Kopie aus dem OP, und wenn Sie etwas bei gedrückter Strg-C-Taste drücken, interessiert Sie der Exit-Code normalerweise nicht wirklich, da etwas anderes bereits schrecklich schief gelaufen ist.
Dan Hogan
1
Wenn der Benutzer das Python-Programm über ein Bash-Skript aufruft und es set -eim Skript verwendet (wie es sollte), möchten Sie das gesamte Bash-Skript unterbrechen, nachdem der Benutzer STRG + C gedrückt hat. Dies würde die Rückgabe eines Exit-Codes ungleich Null erfordern.
Max
6

Sie können SIGINTs nach dem Start des Herunterfahrens ignorieren, indem signal.signal(signal.SIGINT, signal.SIG_IGN)Sie aufrufen, bevor Sie Ihren Bereinigungscode starten.

Dan Getz
quelle