43

次のようなウィンドウで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:がまったく実行されていないということです。出力される唯一のものは「クリーンアップ...」であり、その後、次のようなトレースバックが出力されます。

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

そのため、例外処理コードは実行されておらず、トレースバックは、finally 節で KeyboardInterrupt が発生したと主張していますが、そもそも ctrl-c を押すことがその部分を実行させた原因であるため、これは意味がありません! ジェネリック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」ブロック内のどこでも例外を処理できなければなりません。ただし、印刷は正常に機能しません。その後、本来のように改行が印刷されません! 「中断」は同じ行に印刷されています...その前に何らかの理由でスペースがあります...?とにかく、その後、コードは想定どおりに動作します。

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

4

6 に答える 6

22

非同期例外処理は、残念ながら信頼できません (シグナル ハンドラによって発生した例外、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() 呼び出しはシグナルを見て保釈しますが、ハンドラーの登録を解除した後にシグナルを再発生させます。インタープリターのコードベースを検査しないと、確かなことはわかりません。

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

編集2

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

再びより多くの理論...(コードを読むことに基づいて

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()」関数の適切な類似物であるかどうかはまだ明らかではないため、これ以上深く掘り下げる価値はほとんどないと思います。(複数のバグが発生している可能性があります)

于 2011-01-05T17:34:51.550 に答える
1

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!"

これは、キーボード入力を認識するためにstdinへの別の呼び出しを引き起こしているstdinのバッファリングが行われていることを示唆しています

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

問題はないようです。

dostuff()stdinから読んでいますか?

于 2011-01-05T18:55:14.823 に答える
1

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

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()
于 2014-07-15T16:54:11.117 に答える
0

何が起こっているかについての推測は次のとおりです。

  • Ctrl-C を押すと、"print" ステートメントが壊れます (何らかの理由で... バグ? Win32 の制限?)
  • Ctrl-C を押すと、dostuff() で最初の KeyboardInterrupt もスローされます。
  • 例外ハンドラーが実行され、"Interrupted" を出力しようとしますが、"print" ステートメントが壊れており、別の KeyboardInterrupt をスローします。
  • finally 句が実行され、"cleaning up...." を出力しようとしますが、"print" ステートメントが壊れて、さらに別の KeyboardInterrupt がスローされます。
于 2011-01-05T17:23:24.003 に答える
0

これは私のために働く:

import sys

if __name__ == "__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."
于 2011-01-05T18:22:10.793 に答える