try-exceptなしでPythonでキーボード割り込みをキャプチャする


101

KeyboardInterruptすべてのコードをtry- exceptステートメント内に配置せずにPythonでイベントをキャプチャする方法はありますか?

ユーザーがCtrl+を押した場合、トレースなしで完全に終了したいC

回答:


149

はい、モジュールシグナルを使用して割り込みハンドラーをインストールし、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()

10
シグナルモジュールにはプラットフォーム固有の問題があることに注意してください-このポスターには影響しませんが、「Windowsでは、signal()はSIGABRT、SIGFPE、SIGILL、SIGINT、SIGSEGV、またはSIGTERMでのみ呼び出すことができます。AValueErrorそれ以外の場合は発生します。」
bgporter 2010年

7
スレッドでもうまく機能します。でもwhile True: continue、絶対にやらないでほしい。(そのスタイルでwhile True: passは、とにかくすっきりします。)それは非常に無駄です。のようなものを試してくださいwhile True: time.sleep(60 * 60 * 24)(一度に1日寝ることは完全に恣意的な数字です)。
Chris Morgan

1
time(必要に応じて)Chris Morganの使用の提案を使用している場合は、忘れずにimport time:)
Seaux

1
sys.exit(0)を呼び出すと、SystemExit例外がトリガーされます。あなたはこのとの組み合わせでそれを使用する場合、それはうまく動作させることができます。stackoverflow.com/a/13723190/353094
leetNightshade

2
繰り返しスリープする代わりに、signal.pause()を使用できます
Croad Langshan

36

トレースバックを表示したくない場合は、次のようにコードを作成します。

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


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

(はい、これは質問に直接答えることはありませんが、try / exceptブロックの必要性が不愉快である理由は本当に明確ではありません-これにより、OPの煩わしさが軽減される可能性があります)


5
何らかの理由で、これは常に私のために働くわけではありません。 signal.signal( signal.SIGINT, lambda s, f : sys.exit(0))常にそうします。
Hal Canary

これは、スレッドを使用するpygtkなどでは常に機能するとは限りません。^ Cがプロセス全体ではなく現在のスレッドを強制終了する場合があるため、例外はそのスレッドを介してのみ伝播します。
Sudo Bash

:特にはPyGTKでのCtrl + Cについては、別のSOの質問がありますstackoverflow.com/questions/16410852/...
bgporter

30

独自のシグナルハンドラーを設定する代わりに、コンテキストマネージャーを使用して例外をキャッチし、無視します。

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

これにより、try- exceptブロックが削除されますが、何が起こっているかについての明示的な言及が保持されます。

これにより、信号ハンドラを毎回設定およびリセットする必要がなく、コードの一部のみで割り込みを無視することもできます。


1
いいですね、このソリューションは、信号を扱うよりも目的を表現するのに少し直接的に思えます。
Seaux 2013年

マルチプロセッシングライブラリを使用して、これらのメソッドを追加する必要があるオブジェクトがわかりません。
ステファン・

@Stéphaneどういう意味ですか?マルチプロセッシングを処理する場合、シグナルは親プロセスと子プロセスの両方でトリガーされる可能性があるため、両方で処理する必要があります。それは本当にあなたが何をしているか、そしてあなたのソフトウェアがどのように使われるかに依存します。
バクリウ

8

これは古い質問であることはわかっていますが、最初にここに来てからatexitモジュールを発見しました。そのクロスプラットフォームの実績や警告の完全なリストについてはまだ知りませんが、これまでのところKeyboardInterrupt、Linuxでポストクリーンアップを処理するために探していたものとまったく同じです。問題に取り組む別の方法で投げたかっただけです。

私は、Fabricオペレーションのコンテキストで終了後のクリーンアップを実行したいので、try/ exceptですべてをラップすることも、私にとってオプションではありませんでした。atexitあなたのコードが制御フローのトップレベルにないような状況では、私はうまく合うかもしれません。

atexit たとえば、次のように、箱から出してすぐに読むことができます

import atexit

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

atexit.register(goodbye)

これをデコレータとして使用することもできます(2.6以降。この例はドキュメントからのものです):

import atexit

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

それだけにKeyboardInterrupt限定したい場合は、この質問に対する別の人の答えの方が良いでしょう。

ただし、atexitモジュールは約70行のコードであり、例外を別の方法で処理する同様のバージョンを作成することは難しくありません。たとえば、例外を引数としてコールバック関数に渡します。(その制限によりatexit、変更されたバージョンが保証されます。現在、exit-callback-functionsが例外について知る方法は考えられません。atexitハンドラが例外をキャッチし、コールバックを呼び出してから、再度レイズしますその例外です。ただし、これは別の方法で行うことができます。)

詳細については、以下を参照してください。


atexitがKeyboardInterrupt(python 3.7)で機能しない
TimZaman

ここでKeyboardInterruptのために働いた(python 3.7、MacOS)。多分プラットフォーム固有の癖?
Niko Nyman

4

KeyboardInterruptなしでスタックトレースを出力しないようにすることができますtry: ... except KeyboardInterrupt: pass(最も明白で適切な「最善の」解決策ですが、すでにそれを知っており、他に何かを求めています)sys.excepthook。何かのようなもの

def custom_excepthook(type, value, traceback):
    if type is KeyboardInterrupt:
        return # do nothing
    else:
        sys.__excepthook__(type, value, traceback)

ユーザーがctrl-cを押した場合、トレースなしできれいに終了したい
Alex

7
これはまったく真実ではありません。KeyboardInterrupt例外は、割り込みハンドラー中に作成されます。SIGINTのデフォルトハンドラーは、KeyboardInterruptを発生させるので、その動作を望まない場合は、SIGINTに別のシグナルハンドラーを提供するだけで済みます。例外はtry / exceptでのみ処理できるという点で正しいですが、この場合は、最初から例外が発生しないようにすることができます。
Matt

1
ええ、私は投稿から約3分後にコトリンスキーの答えが出てきたことを学びました;)

2

皆さんから提案された解決策を試しましたが、実際に機能させるにはコードを自分で即興で作成する必要がありました。以下は私の即興コードです:

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

0

誰かが素早く最小限の解決策を探しているなら、

import signal

# The code which crashes program on interruption

signal.signal(signal.SIGINT, call_this_function_if_interrupted)

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