Erfassen Sie Tastaturinterrupts in Python ohne Try-Except

101

Gibt es in Python eine Möglichkeit, KeyboardInterruptEreignisse zu erfassen , ohne den gesamten Code in eine try- except-Anweisung einzufügen?

Ich möchte sauber und spurlos beenden, wenn der Benutzer Ctrl+ drückt C.

Alex
quelle

Antworten:

149

Ja, können Sie einen Interrupt - Handler mit dem Modul installieren Signal , und warten Sie für immer ein mit threading.Event :

import signal
import sys
import time
import threading

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

signal.signal(signal.SIGINT, signal_handler)
print('Press Ctrl+C')
forever = threading.Event()
forever.wait()
Johan Kotlinski
quelle
10
Beachten Sie, dass es einige plattformspezifische Probleme mit dem Signalmodul gibt - sollte dieses Poster nicht betreffen, aber "Unter Windows kann signal () nur mit SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV oder SIGTERM aufgerufen werden. Ein ValueError wird in jedem anderen Fall angehoben. "
Bgporter
7
Funktioniert auch gut mit Threads. Ich hoffe du tust es aber nie while True: continue. (In diesem Stil while True: passwäre es sowieso ordentlicher.) Das wäre sehr verschwenderisch; Versuchen Sie etwas wie while True: time.sleep(60 * 60 * 24)(einen Tag zu schlafen ist eine völlig willkürliche Zahl).
Chris Morgan
1
Wenn Sie Chris Morgans Vorschlag verwenden time(wie Sie sollten), vergessen Sie nicht import time:)
Seaux
1
Das Aufrufen von sys.exit (0) löst für mich eine SystemExit-Ausnahme aus. Sie können es gut funktionieren lassen, wenn Sie es in Kombination mit diesem verwenden: stackoverflow.com/a/13723190/353094
leetNightshade
2
Sie können signal.pause () verwenden, anstatt wiederholt zu schlafen
Croad Langshan
36

Wenn Sie den Traceback nur nicht anzeigen möchten, erstellen Sie Ihren Code folgendermaßen:

## all your app logic here
def main():
   ## whatever your app does.


if __name__ == "__main__":
   try:
      main()
   except KeyboardInterrupt:
      # do nothing here
      pass

(Ja, ich weiß, dass dies die Frage nicht direkt beantwortet, aber es ist nicht wirklich klar, warum die Notwendigkeit eines Try / Except-Blocks zu beanstanden ist - vielleicht macht es das OP weniger nervig)

bgporter
quelle
5
Aus irgendeinem Grund funktioniert das bei mir nicht immer. signal.signal( signal.SIGINT, lambda s, f : sys.exit(0))macht immer.
Hal Canary
Dies funktioniert nicht immer mit Dingen wie pygtk, die Threads verwenden. Manchmal beendet ^ C nur den aktuellen Thread anstelle des gesamten Prozesses, sodass sich die Ausnahme nur über diesen Thread ausbreitet.
Sudo Bash
Es gibt eine weitere SO-Frage speziell zu Strg + C mit pygtk: stackoverflow.com/questions/16410852/…
bgporter
30

Eine Alternative zum Festlegen eines eigenen Signalhandlers besteht darin, einen Kontextmanager zu verwenden, um die Ausnahme abzufangen und zu ignorieren:

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

Dadurch wird der Block try- entfernt except, während explizit erwähnt wird, was gerade passiert.

Auf diese Weise können Sie den Interrupt auch nur in einigen Teilen Ihres Codes ignorieren, ohne die Signalhandler jedes Mal neu einstellen und zurücksetzen zu müssen.

Bakuriu
quelle
1
Schön, diese Lösung scheint etwas direkter darin zu sein, den Zweck auszudrücken, als mit Signalen umzugehen.
Seaux
Bei Verwendung der Multiprozessor-Bibliothek bin ich mir nicht sicher, zu welchem ​​Objekt ich diese Methoden hinzufügen soll. Gibt es einen Hinweis?
Stéphane
@ Stéphane Was meinst du? Wenn Sie sich mit Multiprocessing befassen, müssen Sie das Signal sowohl im übergeordneten als auch im untergeordneten Prozess verarbeiten, da es möglicherweise in beiden Prozessen ausgelöst wird. Es hängt wirklich davon ab, was Sie tun und wie Ihre Software verwendet wird.
Bakuriu
8

Ich weiß, dass dies eine alte Frage ist, aber ich bin zuerst hierher gekommen und habe dann das atexitModul entdeckt. Ich weiß noch nichts über die plattformübergreifende Erfolgsbilanz oder eine vollständige Liste von Vorbehalten, aber bisher war es genau das, wonach ich gesucht habe, um die Nachbereinigung KeyboardInterruptunter Linux durchzuführen. Ich wollte nur eine andere Herangehensweise an das Problem einwerfen.

Ich möchte eine Bereinigung nach dem Beenden im Rahmen von Fabric-Vorgängen durchführen, daher war es für mich auch keine Option , alles in try/ excepteinzuwickeln. Ich denke, atexitdass dies in einer solchen Situation gut passt, in der sich Ihr Code nicht auf der obersten Ebene des Kontrollflusses befindet.

atexit ist sehr leistungsfähig und sofort lesbar, zum Beispiel:

import atexit

def goodbye():
    print "You are now leaving the Python sector."

atexit.register(goodbye)

Sie können es auch als Dekorateur verwenden (ab 2.6; dieses Beispiel stammt aus den Dokumenten):

import atexit

@atexit.register
def goodbye():
    print "You are now leaving the Python sector."

Wenn Sie es nur spezifisch machen möchten, KeyboardInterruptist die Antwort einer anderen Person auf diese Frage wahrscheinlich besser.

Beachten Sie jedoch, dass das atexitModul nur ~ 70 Codezeilen umfasst und es nicht schwierig ist, eine ähnliche Version zu erstellen, die Ausnahmen unterschiedlich behandelt, z. B. die Ausnahmen als Argumente an die Rückruffunktionen zu übergeben. (Die Einschränkung atexitwürde eine modifizierte Version rechtfertigen: Derzeit kann ich mir keine Möglichkeit vorstellen, wie die Exit-Callback-Funktionen über die Ausnahmen Bescheid wissen können. Der atexitHandler fängt die Ausnahme ab, ruft Ihre Rückrufe auf und löst sie dann erneut aus diese Ausnahme. Aber Sie könnten dies anders machen.)

Weitere Informationen finden Sie unter:

Driftfänger
quelle
atexit funktioniert nicht für KeyboardInterrupt (Python 3.7)
TimZaman
Arbeitete hier für KeyboardInterrupt (Python 3.7, MacOS). Vielleicht eine plattformspezifische Eigenart?
Niko Nyman
4

Sie können verhindern, dass eine Stapelverfolgung KeyboardInterruptohne try: ... except KeyboardInterrupt: pass(die offensichtlichste und wahrscheinlich "beste" Lösung, aber Sie kennen sie bereits und haben nach etwas anderem gefragt) gedruckt wird, indem Sie sie ersetzen sys.excepthook. Etwas wie

def custom_excepthook(type, value, traceback):
    if type is KeyboardInterrupt:
        return # do nothing
    else:
        sys.__excepthook__(type, value, traceback)
Richard
quelle
Ich möchte sauberes Beenden ohne Spur, wenn Benutzer Strg-C
Alex
7
Dies ist überhaupt nicht wahr. Die KeyboardInterrupt-Ausnahme wird während eines Interrupt-Handlers erstellt. Der Standardhandler für SIGINT löst den KeyboardInterrupt aus. Wenn Sie dieses Verhalten nicht möchten, müssen Sie lediglich einen anderen Signalhandler für SIGINT bereitstellen. Sie haben insofern Recht, als Ausnahmen nur in einem Versuch behandelt werden können / außer in diesem Fall können Sie jedoch verhindern, dass die Ausnahme überhaupt erst ausgelöst wird.
Matt
1
Ja, das habe ich ungefähr drei Minuten nach dem Posten erfahren, als Kotlinskis Antwort eintraf;)
2

Ich habe die vorgeschlagenen Lösungen von allen ausprobiert, aber ich musste den Code selbst improvisieren, damit er tatsächlich funktioniert. Folgendes ist mein improvisierter Code:

import signal
import sys
import time

def signal_handler(signal, frame):
    print('You pressed Ctrl+C!')
    print(signal) # Value is 2 for CTRL + C
    print(frame) # Where your execution of program is at moment - the Line Number
    sys.exit(0)

#Assign Handler Function
signal.signal(signal.SIGINT, signal_handler)

# Simple Time Loop of 5 Seconds
secondsCount = 5
print('Press Ctrl+C in next '+str(secondsCount))
timeLoopRun = True 
while timeLoopRun:  
    time.sleep(1)
    if secondsCount < 1:
        timeLoopRun = False
    print('Closing in '+ str(secondsCount)+ ' seconds')
    secondsCount = secondsCount - 1
Rohit Jain
quelle
0

Wenn jemand auf der Suche nach einer schnellen Minimallösung ist,

import signal

# The code which crashes program on interruption

signal.signal(signal.SIGINT, call_this_function_if_interrupted)

# The code skipped if interrupted
tejasvi88
quelle