完全なコード ファイルは次のとおりです: Code
1.ソースコード
Python スクリプトを作成すると、それは人間が読めるテキストになります。このソース コードはすべての出発点です。
.py ファイルに記述された Python ソース コードは、人間が判読可能です。このコードは、プログラムの動作を定義し、変数、関数、ループなどを指定します。
2.バイトコードへのコンパイル (コンパイラー)
Python プログラムを実行するときの最初のステップは、ソース コードをバイトコードにコンパイルすることです。これは Python インタプリタによって行われます:
-
構文チェック: 構文エラーがないことを確認します。
-
コンパイル: 高レベルのソース コードを、下位レベルのプラットフォームに依存しない表現であるバイトコードに変換します。このバイトコードは通常、__pycache__ ディレクトリ内の .pyc ファイルに存在します。
-
コンパイラー: Python はインタープリターを使用しますが、最初にソース コードをバイトコードとして知られる低レベルの形式にコンパイルします。
-
トークン化: コードをトークン (キーワード、演算子、識別子など) と呼ばれる小さな部分に分割します。
-
解析: トークンを分析して、Python の構文ルールに従っていることを確認します。
-
制御フロー グラフ (CFG): プログラムの実行中にプログラムを通過する可能性のあるすべてのパスを表します。
-
バイトコード生成: 解析されたトークンを、Python 仮想マシン (PVM) の一連の命令であるバイトコードに変換します。
これについて詳しく見ていきましょう。
Python コンパイラー: Python はインタープリタ言語として知られていますが、コンパイル手順があります。内訳は次のとおりです:
トークン化:
- コードをトークン (キーワード、演算子、識別子など) と呼ばれる小さな部分に分割します。
-
ソース コード: 作成したコードから始まります。
-
トークナイザー (Lexer): ソース コードを、キーワード (for、if)、演算子 ( 、 -)、識別子 (変数名)、リテラル (数値や文字列など) などのトークンと呼ばれる小さな部分に分割します。 .
-
解析: トークンを分析して、Python の構文ルールに従っていることを確認します。
-
構文分析: パーサーはこれらのトークンを取得し、Python の文法規則と照合してチェックします。
-
解析ツリー: コードの文法構造を表すトークンからツリー構造を構築します。
-
セマンティック分析: データ型、スコープ、その他のコンテキスト固有のルールの観点からコードが意味をなしていることを確認します。
-
制御フロー グラフ (CFG): プログラムの実行中にプログラムを通過する可能性のあるすべてのパスを表します。
-
制御フロー グラフ: 実行中にコードを通過する可能性のあるすべてのパスを表します。
-
ノードとエッジ: 各ノードはコードの基本ブロックを表し、エッジはあるブロックから別のブロックへの制御の流れを表します。
-
バイトコード生成: 解析されたトークンを、Python 仮想マシン (PVM) の一連の命令であるバイトコードに変換します。
- バイトコードは、ソース コードをよりコンパクトに低レベルで表現したもので、実行用に最適化されています。プラットフォームに依存しないため、互換性のある PVM を備えた任意のシステムで実行できます。
-
バイトコード: 解析されたコードは、下位レベルのプラットフォームに依存しない表現であるバイトコードに変換されます。
-
命令セット: このバイトコードは、Python 仮想マシン (PVM) が実行できる命令のセットです。今後の実行を高速化するために、バイトコードは __pycache__ ディレクトリの .pyc ファイルに保存されます。
3.バイトコード(バイトコード)をロード中
コンパイル後、Python 仮想マシンはバイトコードをロードします。
-
キャッシュからの読み取り: バイトコードが以前にコンパイルされており、変更されていない場合は、キャッシュ (__pycache__) から読み取られます。これにより、コンパイル手順がスキップされ、実行が高速化されます。
- バイトコードがメモリにロードされ、実行できるようになります。その後、バイトコードは PVM によって実行され、プログラムのタスクを実行するための命令が解釈されます。
4. PVM(PVM)による実行
PVM はバイトコードを解釈して実行します。
-
命令実行: PVM は各バイトコード命令を読み取り、実行します。各命令は、値のロード、演算の実行、関数の呼び出しなどの特定の操作に対応します。
-
メモリ管理: 変数とオブジェクトのメモリの割り当てと割り当て解除を管理します。
Python でのメモリ管理:
-
参照カウント: Python は、メモリ内のオブジェクトへの参照の数を追跡します。参照カウントがゼロになると、オブジェクトによって占有されていたメモリを再利用できます。
-
オブジェクトの割り当て: コードの実行時に、Python オブジェクト (整数、文字列、リストなど) がメモリ内に作成されます。
-
ガベージ コレクション: Python には、使用されなくなったメモリ (つまり、参照カウントがゼロのオブジェクト) の割り当てを解除することでメモリの管理に役立つガベージ コレクタがあります。
-
メモリ プーリング: Python はメモリのプールを使用して、小さなオブジェクトをより効率的に割り当てます。このプーリングは、メモリの小さなチャンクを頻繁に割り当てたり割り当て解除したりするオーバーヘッドを軽減するのに役立ちます。
-
メモリの最適化: Python は、次のようなさまざまな最適化を適用してメモリ使用量を最小限に抑えます。
- PVM は、一部の実装 (PyPy など) でのジャストインタイム (JIT) コンパイルなど、効率を向上させるためにさまざまな実行時の最適化を実行します。
- 小さな整数とインターンされた文字列を再利用します。
- データ構造 (タプル、リスト、辞書など) を効率的に管理します。
例:
-
バイトコード キャッシュ: PVM は、毎回ソース コードを再コンパイルすることを避けるために、コンパイルされたバイトコードをキャッシュします。これにより、以降の実行が高速化されます。
-
定数の折りたたみ: これには、実行時ではなくコンパイル時に定数式を簡素化することが含まれます。たとえば、3 * 2 は 6 に事前計算される可能性があります。
要約すると、PVM はオーケストラの指揮者のようなもので、バイトコードをコンピュータが実行できるアクションにシームレスに変換します。この素晴らしい点は、PVM のおかげで Python コードが移植可能であり、変更せずにさまざまなプラットフォームで実行できることです。
バイトコードが生成されたかどうかを確認するにはどうすればよいでしょうか?
Python モジュールをインポートすると、Python はソース コードをバイトコードにコンパイルし、__pycache__ ディレクトリに保存します。これにより、モジュールをインポートするたびに再コンパイルする必要がなくなり、今後のインポートが高速化されます。
プロセスは次のとおりです:
-
最初のインポート: 初めてモジュールをインポートすると、Python は .py ファイルをバイトコードにコンパイルします。
-
pycache ディレクトリ: バイトコードは、 module_name.cpython-312.pyc のような名前の __pycache__ ディレクトリに保存されます。 #312 は Python のバージョンです。
-
後続のインポート: 後続のインポートでは、Python はコンパイルされたバイトコードの __pycache__ ディレクトリをチェックし、ソース コードが変更されていない場合はそれを使用するため、インポート プロセスが高速化されます。
例:
byte.py があります。 byte.py の実行後に hello_world.py からコードをインポートすると、その特定のフォルダーに __pycache__ ディレクトリが存在し、.pyc ファイルが表示されることがわかります:
from hello_world import greet
greet("Byte code")
py_compile を使用する
py_compile モジュール。Python ソース ファイルをバイトコード ファイルにコンパイルできます。これは、将来のスクリプトの実行を高速化する便利な方法です。
byte.py内
import py_compile
py_compile.compile('hello_world.py')
- py_compile モジュールは hello_world.py をバイトコードにコンパイルします。
- 結果のバイトコードは pycache ディレクトリに保存され、hello_world.cpython-38.pyc (または Python のバージョンに応じて同様のファイル) という名前のファイルが作成されます。
バイトコードの生成:
- スクリプト全体が実行されてバイトコードが生成されます。これは、コンパイル プロセス中にトップレベルのコード (print("Hello, World!") や print("c") など) が実行されることを意味します。
結果のバイトコード:
- バイトコードにはすべての関数、クラス、実行可能ステートメントが含まれており、Python はこれらを使用して今後のスクリプトのインポートを高速化します。
ディスモジュール
Python の dis モジュールは、バイトコードをより読みやすい形式に逆アセンブルするために使用されます。これは、Python コードが内部で何を行っているかを理解するのに役立ちます。これは、Python の内部をデバッグしたり学習したりする場合に特に役立ちます。
from hello_world import greet
greet("Byte code")
出力
import py_compile
py_compile.compile('hello_world.py')
- プログラムは、CPython バイトコードを分析するための強力なツールである dis モジュールをインポートすることから始まります。 CPython は Python のデフォルト実装であり、バイトコードは Python インタプリタの中間言語です。
- 次に、greet という単純な関数を定義しました。この関数はパラメータ名を受け取り、挨拶を出力します。関数自体は非常に単純ですが、Python の内部で起こっていることは、表面で見えるよりも複雑です。
- disassemble_function 関数は、dis.dis() を使用して、greet 関数を逆アセンブルします。 dis.dis() は、Python 関数を、Python の仮想マシンが実際に実行する低レベルのバイトコードに変換します。このバイトコードはPythonによるgreet関数の解釈であり、マシンコードに一歩近づきます。
- スクリプトが disassemble_function() を呼び出すと、コンソール出力には、greet 関数のバイトコードが表示されます。
バイトコードからわかることは次のとおりです:
- LOAD_GLOBAL(0): このオペコードは、グローバル変数 (この場合は print 関数) をロードするために使用されます。
- LOAD_CONST(1): 定数値「Hello,」をスタックにロードします。
- LOAD_FAST(0): このオペコードは、ローカル変数名をスタックにロードします。
- FORMAT_VALUE(0): これは名前文字列をフォーマットし、これから構築される文字列に挿入できるように準備します。
- BUILD_STRING(2): これはスタック上の上位 2 つの値 ('Hello,' と名前) を取得し、最終的な文字列を構築します。
- CALL_FUNCTION(1): この行は、括弧内に引数の数を指定して関数 (スタックにロードしたグローバル印刷関数) を呼び出します (引数は 1 つで、フォーマットされた文字列です)。
- POP_TOP: スタックの最上位を削除します (print は None を返すため、前の呼び出しの結果)。
- LOAD_CONST(0): ロードなし。
- RETURN_VALUE: これは、greet 関数の戻り値です。明示的な return ステートメントがないため、None になります。
- 本質的に、バイトコードは、Python がgreet 関数を実行するために実行する個々の操作を示します。これらの指示を理解することは、Python がどのようにコードを実行し、関数を最適化し、リソースを管理するかを理解するために非常に重要です。これらすべては、Python コードを実行するときに内部でシームレスに行われます。
これは、Python マシンルームへの楽しいダイビングではないでしょうか?コーディングを続けて、この言語のエンジン ルームの奥深くを探索し続けますか?!
以上がPython の内部動作の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。