pythonコンパイラ/インタプリタプロセスをより明確に理解しようとしています。残念ながら、インタプリタのクラスを受講したことも、それらについて多くを読んだこともありません。
基本的に、私が今理解しているのは、Python。pyファイルのコードは最初にpythonバイトコード(私が見る.pycファイルだと思います)にコンパイルされるということです次に、バイトコードは、プロセッサが実際に理解する言語であるマシンコードにコンパイルされます。ほとんどの場合、このスレッドを読みました なぜpythonソースをバイトコードにコンパイルするのか)解釈する前に?
私のコンパイラ/インタプリタの知識はほとんど存在しないことを念頭に置いて、誰かが私にプロセス全体の良い説明をしてくれませんか?または、それが不可能な場合は、コンパイラー/インタープリターの概要を説明するリソースを教えてください。
ありがとう
Pypyなどのエキゾチックな実装を使用していない限り、バイトコードは実際にはマシンコードに解釈されません。
それ以外は、正しい説明があります。バイトコードはPythonランタイムに読み込まれ、仮想マシンによって解釈されます。仮想マシンは、バイトコード内の各命令を読み取り、指定された操作を実行するコードです。このバイトコードは次のように表示されます。次のように、dis
モジュール。
>>> def fib(n): return n if n < 2 else fib(n - 2) + fib(n - 1)
...
>>> fib(10)
55
>>> import dis
>>> dis.dis(fib)
1 0 LOAD_FAST 0 (n)
3 LOAD_CONST 1 (2)
6 COMPARE_OP 0 (<)
9 JUMP_IF_FALSE 5 (to 17)
12 POP_TOP
13 LOAD_FAST 0 (n)
16 RETURN_VALUE
>> 17 POP_TOP
18 LOAD_GLOBAL 0 (fib)
21 LOAD_FAST 0 (n)
24 LOAD_CONST 1 (2)
27 BINARY_SUBTRACT
28 CALL_FUNCTION 1
31 LOAD_GLOBAL 0 (fib)
34 LOAD_FAST 0 (n)
37 LOAD_CONST 2 (1)
40 BINARY_SUBTRACT
41 CALL_FUNCTION 1
44 BINARY_ADD
45 RETURN_VALUE
>>>
上記のコードがCPUによって実行されることは決してないことを理解することは非常に重要です。また、(少なくとも、Pythonの公式C実装ではない)何かに変換されることもありません。 CPUは、バイトコード命令で示される作業を実行する仮想マシンコードを実行します。インタプリタがfib
関数を実行したい場合、インタプリタは一度に1つずつ命令を読み取り、指示されたとおりに実行します。最初の命令LOAD_FAST 0
を調べて、パラメーターが保持されている場所からパラメーター0(n
がfib
に渡される)を取得し、それをインタープリターのスタック(Pythonのインタープリター)にプッシュします。スタックマシンです)。次の命令LOAD_CONST 1
を読み取ると、関数が所有する定数のコレクションから定数番号1(この場合は番号2)を取得し、それをスタックにプッシュします。あなたは実際にこれらの定数を見ることができます:
>>> fib.func_code.co_consts
(None, 2, 1)
次の命令COMPARE_OP 0
は、最上位の2つのスタック要素をポップし、それらの間で不等式比較を実行して、ブール結果をスタックに戻すようにインタープリターに指示します。 4番目の命令は、ブール値に基づいて、5つの命令を前に進めるか、次の命令に進むかを決定します。そのすべての言い回しは、fib
の条件式のif n < 2
部分を説明しています。残りのfib
バイトコードの意味と動作を引き出すことは非常に有益な演習になります。唯一、よくわかりませんがPOP_TOP
です。 JUMP_IF_FALSE
は、ブール引数をポップするのではなくスタックに残すように定義されていると思います。そのため、明示的にポップする必要があります。
さらに有益なのは、生のバイトコードでfib
を検査することです。
>>> code = fib.func_code.co_code
>>> code
'|\x00\x00d\x01\x00j\x00\x00o\x05\x00\x01|\x00\x00S\x01t\x00\x00|\x00\x00d\x01\x00\x18\x83\x01\x00t\x00\x00|\x00\x00d\x02\x00\x18\x83\x01\x00\x17S'
>>> import opcode
>>> op = code[0]
>>> op
'|'
>>> op = ord(op)
>>> op
124
>>> opcode.opname[op]
'LOAD_FAST'
>>>
したがって、バイトコードの最初のバイトがLOAD_FAST
命令であることがわかります。次のバイトのペアである'\x00\x00'
(16ビットの数値0)は、LOAD_FAST
の引数であり、パラメーター0をスタックにロードするようにバイトコードインタープリターに指示します。
すばらしい Marcelo Cantosの答え を完成させるために、逆アセンブルされたバイトコードの出力を説明するための、列ごとの小さな要約を次に示します。
たとえば、この関数が与えられた場合:
def f(num):
if num == 42:
return True
return False
これは(Python 3.6)に分解できます:
(1)|(2)|(3)|(4)| (5) |(6)| (7)
---|---|---|---|----------------------|---|-------
2| | | 0|LOAD_FAST | 0|(num)
|-->| | 2|LOAD_CONST | 1|(42)
| | | 4|COMPARE_OP | 2|(==)
| | | 6|POP_JUMP_IF_FALSE | 12|
| | | | | |
3| | | 8|LOAD_CONST | 2|(True)
| | | 10|RETURN_VALUE | |
| | | | | |
4| |>> | 12|LOAD_CONST | 3|(False)
| | | 14|RETURN_VALUE | |
各列には特定の目的があります。
JUMP
以前の命令からこれまでdis
モジュール で簡単に説明されており、その実装は ceval.c
(CPythonのコアループ)