52

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

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

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

ありがとう

4

2 に答える 2

63

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をスタックにロードするように指示します。

于 2010-07-21T13:28:54.747 に答える
5

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. バイトインデックスに対応するバイトコード内のアドレス(Python 3.6は各命令に2バイトを使用するため、2の倍数ですが、以前のバージョンでは異なる可能性があります)
  5. 命令名(opnameとも呼ばれます)。それぞれがモジュールで簡単に説明されdisおり、その実装はceval.c(CPythonのコアループ)にあります。
  6. いくつかの定数または変数をフェッチしたり、スタックを管理したり、特定の命令にジャンプしたりするためにPythonによって内部的に使用される命令の引数(存在する場合)など。
  7. 命令引数の人間に優しい解釈
于 2017-11-28T10:19:00.703 に答える