1. ホーム
  2. python

[解決済み] sys.excepthook is missing" エラーの対処方法について教えてください。

2022-01-30 14:36:55

質問内容

注意:WindowsやPythonの2.7.3以外のバージョンで以下の問題を再現することは試していません。

この問題を引き起こす最も確実な方法は、以下のテストスクリプトの出力を、以下のようにパイプすることです。 : (以下 bash ):

try:
    for n in range(20):
        print n
except:
    pass

すなわち

% python testscript.py | :
close failed in file object destructor:
sys.excepthook is missing
lost sys.stderr

質問です。

上記のテストスクリプトはどのように修正すればよいのでしょうか のようにスクリプトを実行すると、エラーメッセージを回避することができます(Unix/ bash )?

(テストスクリプトが示すように、このエラーは try-except .)

上の例は非常に人工的なものですが、同じ問題にぶつかっています。 時々 私のスクリプトの出力がサードパーティのソフトウェアにパイプされているとき。

このエラーメッセージは確かに無害なのですが、エンドユーザーにとっては不愉快なものなので、黙らせたいのです。

EDIT: 以下のスクリプトは、上記のオリジナルスクリプトと異なり、sys.excepthookを再定義しているだけで、上記のものと全く同じ動作をします。

import sys
STDERR = sys.stderr
def excepthook(*args):
    print >> STDERR, 'caught'
    print >> STDERR, args

sys.excepthook = excepthook

try:
    for n in range(20):
        print n
except:
    pass

解決方法は?

<ブロッククオート

上記のテストスクリプトをどのように修正すれば、スクリプトを以下のように実行したときにエラーメッセージが表示されないようにできますか(Unix/ bash )?

スクリプトが標準出力に何も書き込まないようにする必要があります。つまり print ステートメントと sys.stdout.write また、それらを呼び出すすべてのコードも同様です。

この現象が起こる理由は、Pythonスクリプトから標準入力から決して読み込まないものに、ゼロでない量の出力をパイプで送っているからです。これは : 標準入力を読まないコマンドにパイプしても同じ結果になります。

python testscript.py | cd .

また、もっと簡単な例として、スクリプトを考えてみましょう。 printer.py を含むだけである。

print 'abcde'

次に

python printer.py | python printer.py

も同じエラーになります。

あるプログラムの出力を別のプログラムにパイプする場合、書き込みプログラムで生成された出力はバッファにバックアップされ、読み込みプログラムがそのデータをバッファから要求するのを待ちます。バッファが空でない限り、書き込み用ファイルオブジェクトを閉じようとすると、エラーで失敗するはずです。これが、あなたが見ているメッセージの根本的な原因です。

このエラーを引き起こす特定のコードは、PythonのC言語実装にあり、そのため、このエラーを try / except ブロック: スクリプトの内容の処理が終了した後に実行されます。基本的に、Pythonが自分自身をシャットダウンしている間、Pythonは以下のものを閉じようとします。 stdout しかし、バッファリングされた出力がまだ読み込まれるのを待っているため、これは失敗です。そこで Python はこのエラーを通常と同じように報告しようとしますが sys.excepthook はすでにファイナライズ処理の一部として削除されているので、これは失敗です。Pythonは次にメッセージを sys.stderr しかし、これはすでに解放されているので、またもや失敗です。画面にメッセージが表示されるのは、Pythonのコードに不測の事態が含まれているためです。 fprintf の出力オブジェクトが存在しない場合でも、ファイルポインタに直接出力を書き出すことができます。

技術的な詳細

この手順の詳細に興味がある方のために、Pythonインタープリタのシャットダウン・シーケンスを実装している Py_Finalize 機能 pythonrun.c .

  1. 終了フックを呼び出し、スレッドをシャットダウンした後、ファイナライズコードは PyImport_Cleanup を使用して、インポートされたすべてのモジュールをファイナライズし、デアロケートします。この関数が実行する最後のタスクは を削除します。 sys モジュール を呼び出すことで、主に構成されています。 _PyModule_Clear のような標準的なストリームオブジェクト (Python オブジェクト) を含む、モジュールの辞書のすべてのエントリをクリアするために使用します。 stdout そして stderr .
  2. ある値が辞書から削除されたり、新しい値に置き換えられたりしたとき。 その参照カウントはデクリメントされます。 を使って その Py_DECREF マクロ . 参照カウントがゼロになったオブジェクトは、割り当て解除の対象となります。このため sys モジュールは標準ストリームオブジェクトへの最後の参照を保持します。 _PyModule_Clear を使用することで、割り当てを解除することができます。 1
  3. Pythonのファイルオブジェクトのデアロケーションは、以下の方法で行います。 その file_dealloc 機能 fileobject.c . この最初の は、Pythonファイルオブジェクトの close メソッド を使用して、適切な名前の close_the_file 機能 :

    ret = close_the_file(f);
    
    

    標準的なファイルオブジェクトの場合。 close_the_file(f) に委ね、C fclose 機能 ファイルポインタに書き込むべきデータがまだある場合は、エラー条件を設定します。 file_dealloc は、そのエラー状態をチェックし、最初に表示されるメッセージを表示します。

    if (!ret) {
        PySys_WriteStderr("close failed in file object destructor:\n");
        PyErr_Print();
    }
    else {
        Py_DECREF(ret);
    }
    
    
  4. このメッセージを表示した後、Pythonは例外を表示するために PyErr_Print . に委譲されます。 PyErr_PrintEx であり、その機能の一部として PyErr_PrintEx からPythonの例外プリンタにアクセスしようとします。 sys.excepthook .

    hook = PySys_GetObject("excepthook");
    
    

    通常のPythonのプログラムで行うのであれば問題ないのですが、このような場合。 sys.excepthook はすでにクリアされています。 2 Pythonはこのエラー状態をチェックし、通知として2番目のメッセージを表示します。

    if (hook && hook != Py_None) {
        ...
    } else {
        PySys_WriteStderr("sys.excepthook is missing\n");
        PyErr_Display(exception, v, tb);
    }
    
    
  5. 欠品のご連絡をいただいた後 excepthook Pythonは例外情報を表示するために PyErr_Display これはスタックトレースを表示するためのデフォルトの方法です。この関数が最初に行うことは、スタックトレースを表示するために sys.stderr .

    PyObject *f = PySys_GetObject("stderr");
    
    

    この場合、それはうまくいきません。 sys.stderr はすでにクリアされており、アクセスできない状態になっています。 3 そこで、このコードでは fprintf を直接送信して、3つ目のメッセージをCの標準エラーストリームに送信しています。

    if (f == NULL || f == Py_None)
        fprintf(stderr, "lost sys.stderr\n");
    
    

興味深いことに、Python 3.4+ では動作が少し変わっています。 標準出力とエラーストリームを明示的にフラッシュします。 組み込みモジュールがクリアされる前に。この方法では、書き込み待ちのデータがある場合、通常のファイナライズ手順で "偶発的" 失敗するのではなく、その状態を明示的に知らせるエラーが表示されるのです。また、もし

python printer.py | python printer.py

をPython 3.4で使用した場合、(括弧を付けてから) print ステートメントを使用すると、まったくエラーが発生しません。2回目のPythonの起動で何らかの理由で標準入力を消費しているのだろうが、それは全く別の問題である。


1 実は、それは嘘です。Pythonのimportの仕組みは は、インポートされた各モジュールの辞書のコピーをキャッシュします。 になるまで解放されません。 _PyImport_Fini が実行されます。 の実装の後半で Py_Finalize および それは 標準ストリームオブジェクトへの最後の参照が消えたとき。参照カウントがゼロになると Py_DECREF オブジェクトを解放します。 すぐに . しかし、本題の答えとして重要なのは、参照が sys モジュールのディクショナリが解放されます。

2 ここでも sys モジュールの辞書は、属性キャッシュ機構のおかげで、実際に何かが割り当て解除される前に完全にクリアされます。Pythonを -vv オプションを使用すると、ファイルポインタを閉じるというエラーメッセージが表示される前に、モジュールのすべての属性がアンセットされるのを確認することができます。

3 この特定の動作の部分だけは、前の脚注で述べた属性キャッシュの仕組みについて知っていなければ意味がないものです。