web-dev-qa-db-ja.com

pythonでKeyboardInterruptを処理できないのはなぜですか?

私はpython 2.6.6のようなコードをウィンドウ上で書いています:

_try:
    dostuff()
except KeyboardInterrupt:
    print "Interrupted!"
except:
    print "Some other exception?"
finally:
    print "cleaning up...."
    print "done."
_

dostuff()は、入力ストリームから一度に1行ずつ読み取り、それに作用する、永久にループする関数です。 ctrl-cを押すと、停止してクリーンアップできるようにしたいと思います。

代わりに起こっているのは、_except KeyboardInterrupt:_の下のコードがまったく実行されていないことです。印刷されるのは "cleaning up ..."だけです。次に、次のようなトレースバックが印刷されます。

_Traceback (most recent call last):
  File "filename.py", line 119, in <module>
    print 'cleaning up...'
KeyboardInterrupt
_

そのため、例外処理コードは実行されておらず、トレースバックは、finally句中にKeyboardInterruptが発生したと主張しています。そもそも走ろう!汎用_except:_句でさえ実行されていません。

編集:コメントに基づいて、_try:_ブロックの内容をsys.stdin.read()に置き換えました。問題はまだ説明したとおりに発生し、_finally:_ブロックの最初の行が実行され、同じトレースバックが出力されます。

編集#2:読み取り後に何かを追加すると、ハンドラーが機能します。したがって、これは失敗します:

_try:
    sys.stdin.read()
except KeyboardInterrupt:
    ...
_

しかし、これは機能します:

_try:
    sys.stdin.read()
    print "Done reading."
except KeyboardInterrupt:
    ...
_

印刷されるものは次のとおりです。

_Done reading. Interrupted!
cleaning up...
done.
_

だから、何らかの理由で、「読み終わった」。前の行で例外が発生した場合でも、行が出力されます。それは本当に問題ではありません-明らかに、 "try"ブロック内のどこでも例外を処理できなければなりません。ただし、印刷は正常に機能しません-後で想定されているように改行を印刷しません! 「Interruped」は同じ行に印刷されます...何らかの理由でその前にスペースがあります...?とにかく、その後、コードは想定どおりに動作します。

これは、ブロックされたシステムコール中の割り込み処理におけるバグのようです。

38
Josh

非同期の例外処理は、残念ながら信頼できません(シグナルハンドラ、C APIを介した外部コンテキストなどによって発生する例外)。非同期例外を適切に処理する可能性を高めるには、コード内でそれらをキャッチするコードの一部について調整が必要です(非常に重要な機能を除き、コールスタックで可能な限り高い部分が適切と思われます)。

呼び出された関数(dostuff)またはスタックのさらに下の関数には、それ自体が考慮しなかった/できなかったKeyboardInterruptまたはBaseExceptionのキャッチがある場合があります。

この些細なケースはpython 2.6.6(x64)インタラクティブ+ Windows 7(64ビット)でうまく動作しました:

>>> import time
>>> def foo():
...     try:
...             time.sleep(100)
...     except KeyboardInterrupt:
...             print "INTERRUPTED!"
...
>>> foo()
INTERRUPTED!  #after pressing ctrl+c

編集:

さらに調査して、他の人が問題を再現するために使用した例だと思うものを試しました。私は怠けていたので、「最終的に」を省きました

>>> def foo():
...     try:
...             sys.stdin.read()
...     except KeyboardInterrupt:
...             print "BLAH"
...
>>> foo()

これは、CTRL + Cを押すとすぐに戻ります。すぐにfooを再び呼び出そうとしたときに興味深いことが起こりました。

>>> foo()

Traceback (most recent call last):
  File "c:\Python26\lib\encodings\cp437.py", line 14, in decode
    def decode(self,input,errors='strict'):
KeyboardInterrupt

Ctrlキーを押しながらCキーを押すことなく、例外がすぐに発生しました。

これは理にかなっているように思われます-Pythonで非同期例外がどのように処理されるかについて、ニュアンスを扱っているようです。非同期例外が実際にポップされ、現在の実行コンテキスト内で発生する前に、いくつかのバイトコード命令を受け取ることができます。 (これは過去にそれを使って遊んだときに見た振る舞いです)

C APIを参照してください: http://docs.python.org/c-api/init.html#PyThreadState_SetAsyncExc

このため、この例のfinallyステートメントの実行のコンテキストでKeyboardInterruptが発生する理由が多少説明されます。

>>> def foo():
...     try:
...             sys.stdin.read()
...     except KeyboardInterrupt:
...             print "interrupt"
...     finally:
...             print "FINALLY"
...
>>> foo()
FINALLY
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in foo
KeyboardInterrupt

インタープリターの標準KeyboardInterrupt/CTRL + Cハンドラーとカスタムシグナルハンドラーが混ざり合って、この種の動作が発生する可能性があります。たとえば、read()呼び出しはシグナルを見てベイルしますが、ハンドラーの登録を解除した後にシグナルを再レイズします。インタプリタのコードベースを調べないと、確実にわかりません。

これが、私が一般的に非同期例外を使用することをためらう理由です...

EDIT 2

バグレポートには良いケースがあると思います。

再びより多くの理論...(コードの読み取りに基づく)ファイルオブジェクトのソースを参照してください: http://svn.python.org/view/python/branches/release26-maint/Objects/fileobject.c?revision = 81277&view = markup

file_readはPy_UniversalNewlineFread()を呼び出します。 freadはerrno = EINTRでエラーを返します(独自のシグナル処理を実行します)。この場合、Py_UniversalNewlineFread()はベイルしますが、PyErr_CheckSignals()を使用してシグナルチェックを実行しないため、ハンドラーを同期的に呼び出すことができます。 file_readはファイルエラーをクリアしますが、PyErr_CheckSignals()も呼び出しません。

使用方法の例については、getline()およびgetline_via_fgets()を参照してください。パターンは、同様の問題に関するこのバグレポートに記載されています:( http://bugs.python.org/issue1195 )。そのため、シグナルはインタプリタによって不確定な時間に処理されるようです。

Sys.stdin.read()の例が「dostuff()」関数の適切な類似物であるかどうかはまだ明確ではないため、さらに深く潜ることにはほとんど価値がないと思います。 (プレイ中に複数のバグがある可能性があります)

21
Jeremy Brown

同様の問題があり、これは私の回避策です:

try:
    some_blocking_io_here() # CTRL-C to interrupt
except:
    try:
        print() # any i/o will get the second KeyboardInterrupt here?
    except:
        real_handler_here()
1
coqem2

sys.stdin.read()はシステムコールであるため、動作はシステムごとに異なります。 Windows 7の場合、入力がバッファリングされているので、sys.stdin.read()がCtrl-Cまですべてを返し、sys.stdinに再びアクセスするとすぐに取得されていると思います「Ctrl-C」を送信します。

以下を試してください、

_def foo():
    try:
        print sys.stdin.read()
        print sys.stdin.closed
    except KeyboardInterrupt:
        print "Interrupted!"
_

これは、標準入力への別の呼び出しがキーボード入力を認識する原因となっている標準入力のバッファリングがあることを示唆しています

_def foo():
    try:
        x=0
        while 1:
            x += 1
        print x
    except KeyboardInterrupt:
        print "Interrupted!"
_

問題はないようです。

dostuff()はstdinから読み取っていますか?

1
milkypostman
def foo():
    try:
        x=0
        while 1:
            x+=1
            print (x)
    except KeyboardInterrupt:
       print ("interrupted!!")
foo()

それはうまくいきます。

0

何が起こっているのかを推測してみましょう。

  • ctrl-Cを押すと、「print」ステートメントが壊れます(何らかの理由で...バグ?Win32の制限?)
  • ctrl-Cを押すと、dostuff()で最初のKeyboardInterruptもスローされます。
  • 例外ハンドラーが実行され、「Interrupted」を印刷しようとしますが、「print」ステートメントが壊れて別のKeyboardInterruptがスローされます。
  • Finally節が実行され、「cleaning up ....」を出力しようとしますが、「print」ステートメントが壊れて、さらに別のKeyboardInterruptがスローされます。
0
user9876

これは私のために働く:

import sys

if __== "__main__":
    try:
        sys.stdin.read()
        print "Here"
    except KeyboardInterrupt:
        print "Worked"
    except:
        print "Something else"
    finally:
        print "Finally"

Dostuff()関数の外側に行を配置するか、ループ条件を関数の外側に移動してみてください。例えば:

try:
    while True:
        dostuff()
except KeyboardInterrupt:
    print "Interrupted!"
except:
    print "Some other exception?"
finally:
    print "cleaning up...."
    print "done."
0
Jake