Wie erfasse ich SIGINT in Python?

535

Ich arbeite an einem Python-Skript, das mehrere Prozesse und Datenbankverbindungen startet. Hin und wieder möchte ich das Skript mit einem Ctrl+ C-Signal beenden und möchte eine Bereinigung durchführen.

In Perl würde ich Folgendes tun:

$SIG{'INT'} = 'exit_gracefully';

sub exit_gracefully {
    print "Caught ^C \n";
    exit (0);
}

Wie mache ich das Analogon dazu in Python?

James Thompson
quelle

Antworten:

787

Registrieren Sie Ihren Handler signal.signalwie folgt:

#!/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 von hier angepasst .

Weitere Dokumentation zu signalfinden Sie hier .  

Matt J.
quelle
13
Können Sie mir sagen, warum ich dies anstelle einer KeyboardInterrupt-Ausnahme verwenden soll? Ist das nicht intuitiver zu bedienen?
Noio
35
Noio: 2 Gründe. Erstens kann SIGINT auf verschiedene Arten an Ihren Prozess gesendet werden (z. B. 'kill -s INT <pid>'). Ich bin nicht sicher, ob KeyboardInterruptException als SIGINT-Handler implementiert ist oder ob es wirklich nur Strg + C-Drücke abfängt, aber in beiden Fällen macht die Verwendung eines Signal-Handlers Ihre Absicht explizit (zumindest, wenn Ihre Absicht mit der von OP identisch ist). Noch wichtiger ist jedoch, dass Sie mit einem Signal nicht alles mit Try-Catches umwickeln müssen, damit sie funktionieren. Dies kann je nach Struktur Ihrer Anwendung mehr oder weniger zu einer Zusammensetzbarkeit und einem allgemeinen Gewinn der Softwareentwicklung führen.
Matt J
35
Beispiel, warum Sie das Signal abfangen möchten, anstatt die Ausnahme abzufangen. Angenommen, Sie führen Ihr Programm aus und leiten die Ausgabe in eine Protokolldatei um ./program.py > output.log. Wenn Sie Strg-C drücken, soll Ihr Programm ordnungsgemäß beendet werden, indem protokolliert wird, dass alle Datendateien gelöscht und als sauber markiert wurden, um zu bestätigen, dass sie in einem bekanntermaßen guten Zustand verbleiben. Strg-C sendet jedoch SIGINT an alle Prozesse in einer Pipeline, sodass die Shell möglicherweise STDOUT (jetzt "output.log") schließt, bevor program.py den Druck des endgültigen Protokolls beendet. Python beschwert sich: "Schließen fehlgeschlagen im Dateiobjekt-Destruktor: Fehler in sys.excepthook:".
Noah Spurrier
24
Beachten Sie, dass signal.pause () unter Windows nicht verfügbar ist. docs.python.org/dev/library/signal.html
Mai Oakes
10
-1 Einhörner für die Verwendung von signal.pause () deuten darauf hin, dass ich bei einem solchen blockierenden Aufruf warten müsste, anstatt echte Arbeit zu leisten. ;)
Nick T
177

Sie können es wie jede andere Ausnahme (KeyboardInterrupt) behandeln. Erstellen Sie eine neue Datei und führen Sie sie mit dem folgenden Inhalt aus Ihrer Shell aus, um zu sehen, was ich meine:

import time, sys

x = 1
while True:
    try:
        print x
        time.sleep(.3)
        x += 1
    except KeyboardInterrupt:
        print "Bye"
        sys.exit()
rledley
quelle
22
Achtung bei der Verwendung dieser Lösung. Sie sollten auch diesen Code verwenden, bevor KeyboardInterrupt catch block: signal.signal(signal.SIGINT, signal.default_int_handler)oder Sie werden scheitern, da KeyboardInterrupt nicht in jeder Situation ausgelöst wird, in der es ausgelöst werden sollte! Details finden Sie hier .
Velda
67

Und als Kontextmanager:

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

Benutzen:

with GracefulInterruptHandler() as h:
    for i in xrange(1000):
        print "..."
        time.sleep(1)
        if h.interrupted:
            print "interrupted!"
            time.sleep(2)
            break

Verschachtelte Handler:

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

Von hier aus: https://gist.github.com/2907502

Udi
quelle
Es könnte auch ein werfen StopIteration, um die innerste Schleife zu durchbrechen, wenn eine Strg-C gedrückt wird, oder?
Theo Belaire
@TheoBelaire Anstatt nur eine StopIteration auszulösen, würde ich einen Generator erstellen, der eine Iterable als Parameter akzeptiert und den Signalhandler registriert / freigibt.
Udi
28

Sie können CTRL+ behandeln, Cindem Sie die KeyboardInterruptAusnahme abfangen . Sie können jeden Bereinigungscode im Ausnahmebehandler implementieren.

Jay Conrod
quelle
21

Aus Pythons Dokumentation :

import signal
import time

def handler(signum, frame):
    print 'Here you go'

signal.signal(signal.SIGINT, handler)

time.sleep(10) # Press Ctrl+c here
Sunqiang
quelle
18

Noch ein Ausschnitt

Wird mainals Hauptfunktion und exit_gracefullyals CTRL+ cHandler bezeichnet

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        pass
    finally:
        exit_gracefully()
Jossef Harush
quelle
4
Sie sollten nur verwenden, außer für Dinge, die nicht passieren sollen. In diesem Fall soll KeyboardInterrupt passieren. Das ist also keine gute Konstruktion.
Tristan
15
@TristanT In jeder anderen Sprache ja, aber in Python sind Ausnahmen nicht nur für Dinge, die nicht passieren sollen. In Python wird es als guter Stil angesehen, Ausnahmen für die Flusskontrolle zu verwenden (falls zutreffend).
Ian Goldby
8

Ich habe den Code von @udi angepasst, um mehrere Signale zu unterstützen (nichts Besonderes):

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

Dieser Code unterstützt den Tastatur-Interrupt-Aufruf ( SIGINT) und den SIGTERM( kill <process>)

Cyril N.
quelle
5

Im Gegensatz zu Matt Js Antwort verwende ich ein einfaches Objekt. Dies gibt mir die Möglichkeit, diesen Handler auf alle Threads zu analysieren, die gestoppt werden müssen.

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)

Anderswo

while True:
    # task
    if handler.SIGINT:
        break
Thomas Devoogdt
quelle
Sie sollten ein Ereignis verwenden oder time.sleep()statt einer Besetztschleife eine Variable ausführen.
OlivierM
@OlivierM Dies ist wirklich anwendungsspezifisch und definitiv nicht der Punkt dieses Beispiels. Wenn Sie beispielsweise Anrufe blockieren oder Funktionen abwarten, ist die CPU nicht beschäftigt. Darüber hinaus ist dies nur ein Beispiel dafür, wie Dinge getan werden können. KeyboardInterrupts sind oft genug, wie in anderen Antworten erwähnt.
Thomas Devoogdt
4

Sie können die Funktionen in Pythons integriertem Signalmodul verwenden , um Signalhandler in Python einzurichten. Insbesondere wird die signal.signal(signalnum, handler)Funktion verwendet, um die handlerFunktion für das Signal zu registrieren signalnum.

Brandon E Taylor
quelle
3

danke für vorhandene Antworten, aber hinzugefügt 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
gsw945
quelle
3

Wenn Sie sicherstellen möchten, dass Ihr Bereinigungsprozess abgeschlossen ist, würde ich die Antwort von Matt J mit einem SIG_IGN ergänzen, damit weitere SIGINTignoriert werden, wodurch verhindert wird, dass Ihre Bereinigung unterbrochen wird.

import signal
import sys

def signal_handler(signum, frame):
    signal.signal(signum, signal.SIG_IGN) # ignore additional signals
    cleanup() # give your process a chance to clean up
    sys.exit(0)

signal.signal(signal.SIGINT, signal_handler) # register the signal with the signal handler first
do_stuff()
Josh Correia
quelle
0

Persönlich konnte ich try / außer KeyboardInterrupt nicht verwenden, da ich den IPC-Modus (Standard Socket) verwendet habe, der blockiert. So wurde der SIGINT aufgerufen, kam aber erst nach Empfang von Daten auf dem Socket.

Das Einstellen eines Signalhandlers verhält sich genauso.

Dies funktioniert jedoch nur für ein tatsächliches Terminal. Andere Startumgebungen akzeptieren möglicherweise nicht Ctrl+C oder verarbeiten das Signal vorab.

Außerdem gibt es in Python "Ausnahmen" und "BaseExceptions", die sich in dem Sinne unterscheiden, dass der Interpreter sich selbst sauber beenden muss, sodass einige Ausnahmen eine höhere Priorität haben als andere (Ausnahmen werden von BaseException abgeleitet).

Hassbit
quelle