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つの命令にジャンプするか、次の命令に進むかを決定します。そのすべてif n < 2
の言い回しは、の条件式の一部を説明していfib
ます。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をスタックにロードするように指示します。