65

Python がどのように機能するかを理解しようとしています (常に使用しているためです!)。私の理解では、python script.py のようなものを実行すると、スクリプトがバイトコードに変換され、インタプリタ/VM/CPython (実際には単なる C プログラム) が python バイトコードを読み取り、それに応じてプログラムを実行します。

このバイトコードはどのように読み込まれますか? C でテキスト ファイルを読み取る方法に似ていますか。Python コードがどのように機械語コードに変換されるのかわかりません。Python インタープリター (CLI の python コマンド) は、実際には既にマシン コードに変換されたプリコンパイル済みの C プログラムであり、Python バイトコード ファイルはそのプログラムを通過するだけなのでしょうか? 言い換えれば、私の Python プログラムは実際には機械語に変換されないのでしょうか? Python インタープリターは既にマシン コードに組み込まれているので、スクリプトを記述する必要はありませんか?

4

4 に答える 4

25

何らかのコード (ソース コード、ライブ関数オブジェクト、コード オブジェクトなど) のバイトコードを見たい場合、disモジュールは必要なものを正確に教えてくれます。例えば:

>>> dis.dis('i/3')
  1           0 LOAD_NAME                0 (i)
              3 LOAD_CONST               0 (3)
              6 BINARY_TRUE_DIVIDE
              7 RETURN_VALUE

ドキュメントではdis、各バイトコードの意味が説明されています。たとえば、LOAD_NAME次のとおりです。

関連付けられた値をco_names[namei]スタックにプッシュします。

これを理解するには、バイトコード インタープリターが仮想スタック マシンであり、それが何でco_namesあるかを理解する必要があります。モジュールのinspectドキュメントには、最も重要な内部オブジェクトの最も重要な属性を示す素晴らしい表があるため、ローカル変数の名前のタプルを保持するオブジェクトco_namesの属性であることがわかります。code言い換えれば、LOAD_NAME 00 番目のローカル変数に関連付けられた値をプッシュします (そして、disこれを調べると、0 番目のローカル変数が という名前であることがわかります'i')。

これで、バイトコードの文字列では不十分であることがわかります。インタープリターは、コード オブジェクトの他の属性も必要とし、場合によっては関数オブジェクトの属性も必要とします (ローカル環境とグローバル環境の元でもあります)。

このinspectモジュールには、ライブ コードをさらに調査するのに役立ついくつかのツールもあります。

これは、多くの興味深いことを理解するのに十分です。nonlocalたとえば、Python はコンパイル時に、関数本体 (およびorglobalステートメント)のどこに変数を割り当てたかに基づいて、関数内の変数がローカルか、クロージャーか、グローバルかを判断することをご存知でしょう。3 つの異なる関数を記述し、それらの逆アセンブリ (および関連するその他の属性) を比較すると、関数が何をしなければならないかを正確に理解することが非常に簡単になります。

(ここで少し難しいのは、クロージャー セルを理解することです。これを実際に理解するには、3 つのレベルの関数が必要です。真ん中の関数が最も内側の関数にどのように転送するかを確認します。)


バイトコードがどのように解釈され、スタック マシンが (CPython で) どのように機能するかを理解するには、ceval.cソース コードを確認する必要があります。thy435 と eyquem による回答は、すでにこれをカバーしています。


ファイルがどのように読み取られるかを理解するpycには、もう少し情報が必要です。Ned Batchelder は、 The structure of .pyc filesという名前の優れた (少し古いかもしれませんが) ブログ投稿を行っています。(3.3 では、インポートに関連する厄介なコードの一部が C から Python に移動されていることに注意してください。これにより、より簡単に理解できるようになります。) しかし、基本的には、ヘッダー情報とモジュールのcodeオブジェクトがmarshal.


ソースがバイトコードにコンパイルされる方法を理解するのは楽しい部分です。

Design of CPython's Compilerは、すべてがどのように機能するかを説明しています。( Python 開発者ガイドの他のセクションのいくつかも役に立ちます。)

初期のもの (トークン化と解析) については、astモジュールを使用して、実際のコンパイルを行う時点までジャンプすることができます。次にcompile.c、その AST がどのようにバイトコードに変換されるかを確認します。

マクロを処理するのは少し難しいかもしれませんが、コンパイラがスタックを使用してブロックに降りる方法と、スタックとそのcompiler_addop仲間を使用して現在のレベルでバイトコードを発行する方法を理解すれば、すべてが理にかなっています。

ほとんどの人が最初に驚くことの 1 つは、関数の動作方法です。関数定義の本体は、コード オブジェクトにコンパイルされます。次に、関数定義自体がコードにコンパイルされ (囲んでいる関数本体、モジュールなどの内部)、実行されると、そのコード オブジェクトから関数オブジェクトが構築されます。(クロージャーがどのように機能する必要があるかを考えれば、なぜそのように機能するのかは明らかです。クロージャーの各インスタンスは、同じコード オブジェクトを持つ個別の関数オブジェクトです。)


これで、CPython にパッチを適用して独自のステートメントを追加する準備が整いましたね。CPython の文法を変更するが示すように、多くのことを正しく行う必要があります (新しいオペコードを作成する必要がある場合は、さらに多くのことがあります)。CPython と同様にPyPyを学び、最初に PyPy のハッキングを開始し、自分が行っていることが理にかなっていて実行可能であることがわかってから CPython に戻る方が簡単だと思うかもしれません。

于 2013-11-11T23:17:37.393 に答える
6

thg4535 の回答を読んで、ceval.c に関する以下の説明が興味深いものになると確信しています:こんにちは、ceval.c!

この記事は、私が一種のファンである Yaniv Aknin によって書かれたシリーズの一部です: Python の内部

于 2013-11-11T22:23:18.460 に答える