PythonでSIGINTをキャプチャするにはどうすればよいですか?


535

私はいくつかのプロセスとデータベース接続を開始するpythonスクリプトに取り組んでいます。時々、Ctrl+ C信号でスクリプトを強制終了したいので、クリーンアップを行いたいと思います。

Perlではこれを行います。

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

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

これをPythonでどうやって行うのですか?

回答:


787

次のsignal.signalようにハンドラを登録します。

#!/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()

ここからコードを改造。

詳細ドキュメントはsignal見つけることができるここに。  


13
KeyboardInterrupt例外の代わりにこれを使用する理由を教えてください。使用する方が直感的ではありませんか?
noio

35
ノイオ:2つの理由。まず、SIGINTはプロセスにいくつでも送信できます(たとえば、 'kill -s INT <pid>')。KeyboardInterruptExceptionがSIGINTハンドラーとして実装されているのか、それともCtrl + Cのプレスのみをキャッチするのかはわかりませんが、どちらの方法でもシグナルハンドラーを使用すると、意図が明示的になります(少なくとも、意図がOPと同じである場合)。さらに重要なのは、シグナルを使用することで、すべてを試行錯誤してそれらを機能させる必要がないことです。アプリケーションの構造によっては、多かれ少なかれ構成可能であり、一般的なソフトウェアエンジニアリングが有利になる場合があります。
Matt J

35
例外をキャッチする代わりに信号をトラップする理由の例。プログラムを実行し、出力をログファイルにリダイレクトするとします./program.py > output.log。押すとCtrlキーを押しながらCを、あなたのプログラムは、それがすべてのデータファイルがフラッシュされ、それらは、既知の良好な状態で放置されている確認するために、クリーンマークしたことをログに記録させることによって適切に終了します。ただし、Ctrl-CはSIGINTをパイプライン内のすべてのプロセスに送信するため、program.pyが最終ログの印刷を完了する前に、シェルがSTDOUT(現在は「output.log」)を閉じる場合があります。Pythonは「ファイルオブジェクトデストラクタのクローズに失敗しました:sys.excepthookのエラー:」と文句を言います。
Noah Spurrier、2011

24
signal.pause()はWindowsでは使用できないことに注意してください。docs.python.org/dev/library/signal.html
May Oakes、

10
signal.pause()を使用するための-1ユニコーンは、実際の作業を行う代わりに、このようなブロッキング呼び出しを待たなければならないことを示唆しています。;)
Nick T

177

他と同様に、例外(KeyboardInterrupt)として扱うことができます。新しいファイルを作成し、次の内容でシェルから実行して、意味を確認します。

import time, sys

x = 1
while True:
    try:
        print x
        time.sleep(.3)
        x += 1
    except KeyboardInterrupt:
        print "Bye"
        sys.exit()

22
このソリューションを使用するときの注意。また、KeyboardInterruptがblock:をキャッチする前にこのコードを使用する必要がありますsignal.signal(signal.SIGINT, signal.default_int_handler)。詳細はこちら
Velda 2016年

67

そしてコンテキストマネージャとして:

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

使用するには:

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

ネストされたハンドラー:

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

ここから:https : //gist.github.com/2907502


StopIterationctrl-Cが押されたときに、最も内側のループを解除するためにa をスローすることもできますよね?
Theo Belaire、2014

@TheoBelaire StopIterationをスローする代わりに、イテラブルをパラメーターとして受け入れ、シグナルハンドラーを登録/解放するジェネレーターを作成します。
Udi、2014

28

CTRL+ を処理Cするには、KeyboardInterrupt例外をキャッチします。例外ハンドラーにクリーンアップコードを実装できます。


21

Pythonのドキュメントから:

import signal
import time

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

signal.signal(signal.SIGINT, handler)

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

18

まだ別のスニペット

言及main主な機能として、およびexit_gracefullyとしてのCTRL+のcハンドラ

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        pass
    finally:
        exit_gracefully()

4
発生するはずのないもの以外は使用しないでください。この場合、KeyboardInterruptが発生するはずです。したがって、これは良い構成ではありません。
トリスタン

15
@TristanT他のどの言語でも可能ですが、Pythonでは例外が発生するはずのないことだけが原因ではありません。フロー制御の例外を使用することは、Pythonで実際に適切なスタイルと見なされます(適切な場合)。
Ian Goldby 2017年

8

私は@udiのコードを複数の信号をサポートするように変更しました(空想はありません):

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

このコードのサポートキーボード割り込みコール(SIGINT)とSIGTERMkill <process>


5

Matt Jの答えとは対照的に、私は単純なオブジェクトを使用しています。これにより、このハンドラーを解析して、セキュリティで停止する必要のあるすべてのスレッドを処理できる可能性があります。

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)

他の場所

while True:
    # task
    if handler.SIGINT:
        break

time.sleep()変数に対してビジーループを実行する代わりに、イベントを使用する必要があります。
OlivierM、

@OlivierMこれは実際にはアプリケーション固有のものであり、この例のポイントではありません。たとえば、呼び出しをブロックしたり、関数を待機したりしても、CPUはビジー状態になりません。さらに、これは物事を行うことができる方法のほんの一例です。他の回答で述べたように、KeyboardInterruptsで十分な場合がよくあります。
Thomas Devoogdt


3

既存の回答に感謝しますが、追加されました 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

3

クリーンアッププロセスが確実に終了するようにしたい場合は、SIG_IGNを使用してMatt Jの回答に追加します。これにより、無視され、クリーンアップが中断されなくなります。SIGINT

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()

0

個人的には、ブロックしている標準ソケット(IPC)モードを使用していたため、try / except KeyboardInterruptを使用できませんでした。したがって、SIGINTはキューに入れられましたが、ソケットでデータを受信した後にのみ来ました。

シグナルハンドラの設定も同じように動作します。

一方、これは実際の端末でのみ機能します。他の開始環境はCtrl+を受け入れないCか、シグナルを事前に処理しない場合があります。

また、Pythonには「例外」と「BaseExceptions」があり、インタプリタ自体が完全に終了する必要があるという意味で異なるため、一部の例外は他よりも高い優先順位を持っています(例外はBaseExceptionから派生しています)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.