あなたが見ている例外は、同じ人によって書かれた、依存astng
するツールキットであるパッケージ (おそらく「抽象構文ツリー、次世代」?)のバグによって引き起こされます。pylint
ついでに言うと、私は常に人々に、可能な場合pyflakes
の代わりに使用することをお勧めしていますpylint
。これは、迅速、単純、高速、予測可能であるためです。一方、pylint
遅いだけでなく、正確に取得できるいくつかの種類の深い魔法を実行しようとしています。こういうトラブル。:)
PyPI の 2 つのパッケージは次のとおりです。
http://pypi.python.org/pypi/pylint
http://pypi.python.org/pypi/astng
そして、この問題は、レポートを生成するためにコードを実行しないため、必然的にコード内のバグでpylint
あり、コード内のバグではないことに注意してください。ファイルなど)!あなたのコードは実行されないため、スレッド化や関数で呼び出しを保護するなどの注意を払っても、このエラーを防ぐことはできなかったでしょう — 他の理由で、これから調査しようとしている動作を変更するためにコード スニペットが発生した場合を除きます。pylint
init()
cleanup()
それで、あなたの実際の例外に進みます。
私は実際に_shutdown
前に聞いたことがありませんでした!Python 標準ライブラリをすばやく検索すると、その定義が示されましthreading.py
たが、どこからでも関数を呼び出すことはできませんでした。pythonrun.c
Python C ソース コードを検索することによってのみ、インタープリターのシャットダウン中に関数が実際に呼び出されている場所を発見しました。
static void
wait_for_thread_shutdown(void)
{
...
PyObject *threading = PyMapping_GetItemString(tstate->interp->modules,
"threading");
if (threading == NULL) {
/* threading not imported */
PyErr_Clear();
return;
}
result = PyObject_CallMethod(threading, "_shutdown", "");
if (result == NULL) {
PyErr_WriteUnraisable(threading);
}
...
}
どうやらこれは、標準ライブラリ モジュールが必要とするある種のクリーンアップ関数でthreading
あり、確実に呼び出されるように Python インタープリター自体を特別なケースにしています。
上記のコードからわかるように、Python はthreading
、プログラムの実行中にモジュールがまったくインポートされない場合でも、静かに問題なく処理します。しかし、threading
インポートされ、シャットダウン時にまだ存在する場合、インタープリターは_shutdown
関数の内部を調べ、エラーメッセージを出力し、問題の原因であるゼロ以外の終了ステータスを返します。呼び出すことはできません。
threading
したがって、モジュールが存在するのに、プログラムの調査が完了し、Python が終了する_shutdown
時点でメソッドがない理由を発見する必要があります。pylint
いくつかの楽器が必要です。終了時にモジュールがどのように見えるかを出力できpylint
ますか? 私たちはできる!モジュールは、最後のpylint/lint.py
数行で、Run
定義したクラスをインスタンス化することで「メイン プログラム」を実行します。
if __name__ == '__main__':
Run(sys.argv[1:])
そこで私lint.py
はエディターで開きました — それぞれの小さなプロジェクトを Python 仮想環境にインストールすることの素晴らしい点の 1 つは、簡単な実験のためにサードパーティのコードに飛び込んで編集できることです — そしてprint
、Run
クラスの__init__()
メソッド:
sys.path.pop(0)
print "*****", sys.modules['threading'].__file__ # added by me!
if exit:
sys.exit(self.linter.msg_status)
コマンドを再実行しました:
python -m pylint.lint m2test.py
そして、モジュールの__file__
文字列が出てきました:threading
***** /home/brandon/venv/lib/python2.7/site-packages/M2Crypto/threading.pyc
まあ、それを見てください。
これが問題です!
このパスによると、実際には、M2Crypto/threading.py
すべての通常の状況下で と呼ばれるだけのモジュールが存在するM2Crypto.threading
ためsys.modules
、次の名前でディクショナリに配置されます。
sys.modules['M2Crypto.threading']
しかし、どういうわけか、そのファイルはメインの Python モジュールとしても読み込まれ、標準ライブラリにthreading
ある公式モジュールを隠しています。threading
このため、Python の終了ロジックは、標準ライブラリ_shutdown()
関数が欠落していることを正確に訴えています。
これはどのように起こりますか?最上位モジュールはsys.path
、その下のサブディレクトリではなく、 に明示的にリストされているパスにのみ表示できます。これは新たな疑問につながります:pylint
実行中に…/M2Crypto/
ディレクトリ自体がsys.path
トップレベルのモジュールを含んでいるかのように配置されるポイントはありますか? どれどれ!
M2Crypto
より多くのインストルメンテーションが必要です:名前に が含まれるディレクトリが に現れる瞬間を Python に教えてもらう必要がありますsys.path
。それは本当に遅くなり__init__.py
ますが、実行時にインポートされる最初のモジュールであるため、-m pylint.lint
pylintsys.path
にtrace 関数を追加しましょう。初期化:
def install_tracer():
import sys
output = open('mytracer.out', 'w')
def mytracer(frame, event, arg):
broken = any(p.endswith('M2Crypto') for p in sys.path)
output.write('{} {}:{} {}\n'.format(
broken, frame.f_code.co_filename, frame.f_lineno, event))
return mytracer
sys.settrace(mytracer)
install_tracer()
del install_tracer
ここで私がいかに注意しているかに注意してください: モジュールの名前空間で名前を 1 つだけ定義し、それを慎重に削除して、pylint
ロードを続行する前にクリーンアップします! そして、トレース関数自体が必要とするすべてのリソース (つまり、sys
モジュールとoutput
開いているファイル) はinstall_tracer()
クロージャーで利用できるため、外側からはpylint
いつもとまったく同じように見えます。誰かがそれを内省しようとする場合に備えて、pylint
そうかもしれません!
これにより、約 80 万行のファイルmytracer.out
が生成され、それぞれ次のようになります。
False /home/brandon/venv/lib/python2.7/posixpath.py:118 call
ファイル名と行番号は実行中のコード行であり、インタープリターが実行のどの段階にあるかをFalse
示します。sys.path
call
それでsys.path
、中毒になることはありますか?True
最初の行またはFalse
各行だけを見て、各値で始まる連続する行の数を見てみましょう。
$ awk '{print$1}' mytracer.out | uniq -c
607997 False
3173 True
4558 False
33217 True
4304 False
41699 True
2953 False
110503 True
52575 False
わお!それは問題だ!一度に数千行の実行の場合、テストケースは ですTrue
。これは、インタープリターが…/M2Crypto/
パスM2Crypto
上で実行されていることを意味します。を含む ディレクトリのみが…/M2Crypto
パス上にあるべきです。False
ファイルで最初に遷移するものを探していると、次のようTrue
に表示されます。
False /home/brandon/venv/lib/python2.7/site-packages/logilab/astng/builder.py:132 line
False /home/brandon/venv/lib/python2.7/posixpath.py:118 call
...
False /home/brandon/venv/lib/python2.7/posixpath.py:124 line
False /home/brandon/venv/lib/python2.7/posixpath.py:124 return
True /home/brandon/venv/lib/python2.7/site-packages/logilab/astng/builder.py:133 line
ファイルの 132 行目と 133 行目を見るとbuilder.py
、犯人が明らかになります。
130 # build astng representation
131 try:
132 sys.path.insert(0, dirname(path)) # XXX (syt) iirk
133 node = self.string_build(data, modname, path)
134 finally:
135 sys.path.pop(0)
コメントに注意してください。これは元のコードの一部であり、私が追加したものではありません! 明らかに、XXX (syt) iirk
このプログラマーの奇妙な母国語では、「このモジュールの親ディレクトリを配置して、誰かがサブモジュールを含むパッケージを強制的にイントロスペクトするたびに不思議なsys.path
ことに壊れるようにする」というフレーズに対する感嘆符です。明らかに、それは非常にコンパクトな母国語です。:)pylint
pylint
threading
sys.modules
の実際のインポートを監視するようにトレース モジュールを調整すると(演習は読者に任せます)、分析中に他の標準ライブラリ モジュールによってインポートされた が、無邪気にインポートしようとしたthreading
ときに発生することがわかります。.SocketServer
threading
それでは、何が起こっているかを確認しましょう。
pylint
危険な魔法です。
- その魔法の一部として、あなたを見つけると、ディスク上で検索して解析し、名前空間から有効な名前または無効な名前をロードしているかどうかを予測
import foo
しようとします。foo.py
- [以下の私のコメントを参照してください。]
.split()
の戻り値を呼び出すためRSA.as_pem()
、はメソッドpylint
をイントロスペクトしようとします。これにより、モジュールが使用され、モジュールが importを誘導する呼び出しを行います。as_pem()
M2Crypto.BIO
pylint
threading
foo.py
moduleのロードの一部として、そのディレクトリが package 内にある場合でも、 onをpylint
含むディレクトリをスローします。したがって、そのディレクトリ内のモジュールに、分析中に同じ名前の標準ライブラリ モジュールをシャドウする権限を与えます。foo.py
sys.path
- Python が終了すると、 のメソッドを実行したいので、
M2Crypto.threading
ライブラリが所属する場所に座っていることに動揺します。threading
_shutdown()
threading
これはバグとして のpylint
/のastng
人々に報告する必要がありlogilab.org
ます。私があなたを送ったと彼らに伝えてください。
これを行った後も使用し続けることにしpylint
た場合、この場合、2 つの解決策があるようです: を呼び出すコードを検査しないかM2Crypto
、インポート プロセスthreading
中にインポートします。モジュールが興奮して代わりにスロットをつかもうとする前に、スロットをつかむ機会を得ること。pylint
import threading
pylint/__init__.py
sys.modules['threading']
pylint
M2Crypto/threading.py
結論として、astng
XXX (syt) irk の作者が最もよく言っていると思います。それはそう。