web-dev-qa-db-ja.com

Pythonコンパイル/解釈プロセス

pythonコンパイラ/インタプリタプロセスをより明確に理解しようとしています。残念ながら、インタプリタのクラスを受講したことも、それらについて多くを読んだこともありません。

基本的に、私が今理解しているのは、Python。pyファイルのコードは最初にpythonバイトコード(私が見る.pycファイルだと思います)にコンパイルされるということです次に、バイトコードは、プロセッサが実際に理解する言語であるマシンコードにコンパイルされます。ほとんどの場合、このスレッドを読みました なぜpythonソースをバイトコードにコンパイルするのか)解釈する前に?

私のコンパイラ/インタプリタの知識はほとんど存在しないことを念頭に置いて、誰かが私にプロセス全体の良い説明をしてくれませんか?または、それが不可能な場合は、コンパイラー/インタープリターの概要を説明するリソースを教えてください。

ありがとう

45
NickHalden

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(nfibに渡される)を取得し、それをインタープリターのスタック(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をスタックにロードするようにバイトコードインタープリターに指示します。

55
Marcelo Cantos

すばらしい 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          |   |

各列には特定の目的があります。

  1. ソースコード内の対応する行番号
  2. オプションで、現在の命令実行されたことを示します(バイトコードが フレームオブジェクト からのものである場合など)
  3. 可能性を示すラベルJUMP以前の命令からこれまで
  4. バイトインデックスに対応するバイトコードのaddress(Python 3.6は各命令に2バイトを使用するため、2の倍数ですが、以前のバージョンでは異なる可能性があります)
  5. 命令名(opnameとも呼ばれます)。それぞれについて disモジュール で簡単に説明されており、その実装は ceval.c (CPythonのコアループ)
  6. argument(存在する場合)Pythonによって内部的に使用され、定数または変数のフェッチ、スタックの管理、特定の命令へのジャンプなどを行います。
  7. 人間に優しい解釈命令引数の
5
Delgan