何らかのコード (ソース コード、ライブ関数オブジェクト、コード オブジェクトなど) のバイトコードを見たい場合、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 0
0 番目のローカル変数に関連付けられた値をプッシュします (そして、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 に戻る方が簡単だと思うかもしれません。