ホームページ >バックエンド開発 >Python チュートリアル >Python 仮想マシンの pyc ファイル構造は何ですか?

Python 仮想マシンの pyc ファイル構造は何ですか?

王林
王林転載
2023-05-27 21:01:581540ブラウズ

    PYC ファイル

    pyc ファイルは、ソース コードを解釈して実行するときに Python によって生成されるバイトコード ファイルで、ソース コードのコンパイル結果が含まれます。 . および関連するメタデータ情報を使用して、Python がコードをより速くロードして実行できるようにします。

    コンパイル言語とは異なり、Python はインタープリタ型言語であり、ソース コードを直接マシン コードにコンパイルして実行することはありません。コードを実行する前に、Python インタープリターはまずソース コードをバイトコードにコンパイルし、次にそのバイトコードを解釈して実行します。 .pyc ファイルは、このプロセス中に生成されるバイトコード ファイルです。

    Python インタープリターは、初めて .py ファイルを実行するときに、同じディレクトリに対応する .pyc ファイルを生成します。これにより、次回ファイルが読み込まれるときにより速く実行できるようになります。ソース ファイルが変更されて再ロードされると、インタープリターは .pyc ファイルを再生成して、キャッシュされたバイトコードを更新します。

    PYC ファイルの生成

    通常の Python ファイルはコンパイラを通じてバイトコードに変換する必要があり、その後バイトコードが Python 仮想マシンに渡され、Python 仮想マシンがバイトコードを実行します。 。全体的なプロセスは次のとおりです。

    Python 仮想マシンの pyc ファイル構造は何ですか?

    #compile all モジュールを直接使用して、対応するファイルの pyc ファイルを生成できます。

    ➜  pvm ls
    demo.py  hello.py
    ➜  pvm python -m compileall .
    Listing '.'...
    Listing './.idea'...
    Listing './.idea/inspectionProfiles'...
    Compiling './demo.py'...
    Compiling './hello.py'...
    ➜  pvm ls
    __pycache__ demo.py     hello.py
    ➜  pvm ls __pycache__ 
    demo.cpython-310.pyc  hello.cpython-310.pyc

    python -m COMPILEALL . このコマンドは、現在のディレクトリ内の py ファイルを再帰的にスキャンし、対応するファイルの pyc ファイルを生成します。

    PYC ファイルのレイアウト

    Python 仮想マシンの pyc ファイル構造は何ですか?

    ##最初の部分 マジック ナンバーは 2 つの部分で構成されます。前編 マジックは 2 バイトの整数と、その他の 2 つの文字 (キャリッジ リターンとライン フィード) で構成され、「\r\n」も 2 バイトの合計 4 バイトを占めます。この 2 バイトの整数は、Python のバージョンによって異なります。たとえば、python3.5 では、この値は 3351 などになり、python3.9 では、この値は 3420、3421、3422、3423、3424 などになります。 Python 3.9 のマイナー バージョン)。

    パート 2 ビット フィールド このフィールドの主な目的は、将来的にコンパイル結果を再現できるようにすることですが、python3.9a2 では、このフィールドの値はすべて 0 のままです。詳細については、PEP552 - 決定論的 pyc を参照してください。このフィールドは、python2 および python3 の初期バージョンには存在しません (python3.5 にはまだ存在しません)。このフィールドは、python3 の以降のバージョンでのみ表示されます。

    Python 仮想マシンの pyc ファイル構造は何ですか?3 番目の部分は、py ソース ファイル全体のサイズです。

    4 番目の部分は、pyc ファイル全体の中で最も重要な部分でもあります。最後の部分は、CodeObject オブジェクトのシリアル化後のデータです。このオブジェクトに関連するデータは後で注意深く分析します。

    それでは、pyc ファイルを詳細に分析しましょう。対応する Python コードは次のとおりです:

    def f():
        x = 1
        return 2

    pyc ファイルの 16 進数形式は次のとおりです:

    ➜  __pycache__ hexdump -C hello.cpython-310.pyc
    00000000  6f 0d 0d 0a 00 00 00 00  b9 48 21 64 20 00 00 00  |o........H!d ...|
    00000010  e3 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
    00000020  00 02 00 00 00 40 00 00  00 73 0c 00 00 00 64 00  |.....@...s....d.|
    00000030  64 01 84 00 5a 00 64 02  53 00 29 03 63 00 00 00  |d...Z.d.S.).c...|
    00000040  00 00 00 00 00 00 00 00  00 01 00 00 00 01 00 00  |................|
    00000050  00 43 00 00 00 73 08 00  00 00 64 01 7d 00 64 02  |.C...s....d.}.d.|
    00000060  53 00 29 03 4e e9 01 00  00 00 e9 02 00 00 00 a9  |S.).N...........|
    00000070  00 29 01 da 01 78 72 03  00 00 00 72 03 00 00 00  |.)...xr....r....|
    00000080  fa 0a 2e 2f 68 65 6c 6c  6f 2e 70 79 da 01 66 01  |.../hello.py..f.|
    00000090  00 00 00 73 04 00 00 00  04 01 04 01 72 06 00 00  |...s........r...|
    000000a0  00 4e 29 01 72 06 00 00  00 72 03 00 00 00 72 03  |.N).r....r....r.|
    000000b0  00 00 00 72 03 00 00 00  72 05 00 00 00 da 08 3c  |...r....r......<|
    000000c0  6d 6f 64 75 6c 65 3e 01  00 00 00 73 02 00 00 00  |module>....s....|
    000000d0  0c 00                                             |..|
    000000d2

    データ使用量のためリトル エンディアン表現、つまり上記のデータの場合:

    マジック ナンバーの最初の部分は 0xa0d0d6f です。

      2 番目の部分のビット フィールドは 0x0 です。
    • 3 番目の部分の最終変更日は 0x642148b9 です。
    • 4 番目の部分のファイル サイズは 0x20 バイトです。これは、hello.py ファイルのサイズが 32 バイトであることを意味します。
    • 以下は、pyc ファイルのヘッダー メタ情報を読み取るための小さなコード スニペットです:
    • import struct
      import time
      import binascii
      fname = "./__pycache__/hello.cpython-310.pyc"
      f = open(fname, "rb")
      magic = struct.unpack(&#39;<l&#39;, f.read(4))[0]
      bit_filed = f.read(4)
      print(f"bit field = {binascii.hexlify(bit_filed)}")
      moddate = f.read(4)
      filesz = f.read(4)
      modtime = time.asctime(time.localtime(struct.unpack(&#39;<l&#39;, moddate)[0]))
      filesz = struct.unpack(&#39;<L&#39;, filesz)
      print("magic %s" % (hex(magic)))
      print("moddate (%s)" % (modtime))
      print("File Size %d" % filesz)
      f.close()

      上記のコードの出力は次のとおりです:

    ビット フィールド = b'00000000'

    magic 0xa0d0d6f

    moddate (Mon Mar 27 15:41:45 2023)

    ファイル サイズ 32

    pyc についてファイル操作の詳細については、Python 標準ライブラリ importlib/_bootstrap_external.py ファイルのソース コードを参照してください。

    CODEOBJECT

    CPython では、
    CodeObject

    は、バイトコード、定数、変数、位置パラメータ、キーワード パラメータ、および Python コードのその他の情報を含むオブジェクトです。ファイル名、コード行番号など、コードの実行に使用されるメタデータ。

    CPython では、Python モジュールまたは関数を実行するとき、インタープリターはまずそのコードを

    CodeObject

    にコンパイルしてから実行します。コンパイル中に、インタープリターは Python コードをバイトコードに変換し、それを CodeObject オブジェクトに保存します。その後、そのモジュールまたは関数を呼び出すたびに、インタープリターは

    CodeObject

    内のバイトコードを使用してコードを実行します。 CodeObject オブジェクトは不変であり、一度作成すると変更することはできません。これは、Python コードのバイトコードが不変であり、CodeObject オブジェクトにはこれらのバイトコードが含まれているため、これも不変であるためです。

    この記事では、主にコード オブジェクトの主な内容とその機能について簡単に紹介しますが、以降の記事では、コード オブジェクトに対応するソース コードと、対応するフィールドの詳細な機能について詳しく説明します。 <p>现在举一个例子来分析一下 pycdemo.py 的 pyc 文件,pycdemo.py 的源程序如下所示:</p><pre class="brush:py;">if __name__ == &amp;#39;__main__&amp;#39;: a = 100 print(a)</pre><p>下面的代码是一个用于加载 pycdemo01.cpython-39.pyc 文件(也就是 hello.py 对应的 pyc 文件)的代码,使用 marshal 读取 pyc 文件里面的 code object 。</p><pre class="brush:py;">import marshal import dis import struct import time import types import binascii def print_metadata(fp): magic = struct.unpack(&amp;#39;&lt;l&amp;#39;, fp.read(4))[0] print(f&quot;magic number = {hex(magic)}&quot;) bit_field = struct.unpack(&amp;#39;&lt;l&amp;#39;, fp.read(4))[0] print(f&quot;bit filed = {bit_field}&quot;) t = struct.unpack(&amp;#39;&lt;l&amp;#39;, fp.read(4))[0] print(f&quot;time = {time.asctime(time.localtime(t))}&quot;) file_size = struct.unpack(&amp;#39;&lt;l&amp;#39;, fp.read(4))[0] print(f&quot;file size = {file_size}&quot;) def show_code(code, indent=&amp;#39;&amp;#39;): print (&quot;%scode&quot; % indent) indent += &amp;#39; &amp;#39; print (&quot;%sargcount %d&quot; % (indent, code.co_argcount)) print (&quot;%snlocals %d&quot; % (indent, code.co_nlocals)) print (&quot;%sstacksize %d&quot; % (indent, code.co_stacksize)) print (&quot;%sflags %04x&quot; % (indent, code.co_flags)) show_hex(&quot;code&quot;, code.co_code, indent=indent) dis.disassemble(code) print (&quot;%sconsts&quot; % indent) for const in code.co_consts: if type(const) == types.CodeType: show_code(const, indent+&amp;#39; &amp;#39;) else: print(&quot; %s%r&quot; % (indent, const)) print(&quot;%snames %r&quot; % (indent, code.co_names)) print(&quot;%svarnames %r&quot; % (indent, code.co_varnames)) print(&quot;%sfreevars %r&quot; % (indent, code.co_freevars)) print(&quot;%scellvars %r&quot; % (indent, code.co_cellvars)) print(&quot;%sfilename %r&quot; % (indent, code.co_filename)) print(&quot;%sname %r&quot; % (indent, code.co_name)) print(&quot;%sfirstlineno %d&quot; % (indent, code.co_firstlineno)) show_hex(&quot;lnotab&quot;, code.co_lnotab, indent=indent) def show_hex(label, h, indent): h = binascii.hexlify(h) if len(h) &lt; 60: print(&quot;%s%s %s&quot; % (indent, label, h)) else: print(&quot;%s%s&quot; % (indent, label)) for i in range(0, len(h), 60): print(&quot;%s %s&quot; % (indent, h[i:i+60])) if __name__ == &amp;#39;__main__&amp;#39;: filename = &quot;./__pycache__/pycdemo01.cpython-39.pyc&quot; with open(filename, &quot;rb&quot;) as fp: print_metadata(fp) code_object = marshal.load(fp) show_code(code_object)</pre><p>执行上面的程序输出结果如下所示:</p><pre class="brush:py;">magic number = 0xa0d0d61 bit filed = 0 time = Tue Mar 28 02:40:20 2023 file size = 54 code argcount 0 nlocals 0 stacksize 2 flags 0040 code b&amp;#39;650064006b02721464015a01650265018301010064025300&amp;#39; 3 0 LOAD_NAME 0 (__name__) 2 LOAD_CONST 0 (&amp;#39;__main__&amp;#39;) 4 COMPARE_OP 2 (==) 6 POP_JUMP_IF_FALSE 20 4 8 LOAD_CONST 1 (100) 10 STORE_NAME 1 (a) 5 12 LOAD_NAME 2 (print) 14 LOAD_NAME 1 (a) 16 CALL_FUNCTION 1 18 POP_TOP &gt;&gt; 20 LOAD_CONST 2 (None) 22 RETURN_VALUE consts &amp;#39;__main__&amp;#39; 100 None names (&amp;#39;__name__&amp;#39;, &amp;#39;a&amp;#39;, &amp;#39;print&amp;#39;) varnames () freevars () cellvars () filename &amp;#39;./pycdemo01.py&amp;#39; name &amp;#39;&lt;module&gt;&amp;#39; firstlineno 3 lnotab b&amp;#39;08010401&amp;#39;</pre><p>下面是 code object 当中各个字段的作用:</p> <ul class=" list-paddingleft-2"> <li><p>首先需要了解一下代码块这个概念,所谓代码块就是一个小的 python 代码,被当做一个小的单元整体执行。在 Python 中常见的代码块包括函数体、类的定义和模块。</p></li> <li><p>argcount,这个表示一个代码块的参数个数,这个参数只对函数体代码块有用,因为函数可能会有参数,比如上面的 pycdemo.py 是一个模块而不是一个函数,因此这个参数对应的值为 0 。</p></li> <li><p>co_code,这个对象的具体内容就是一个字节序列,存储真实的 python 字节码,主要是用于 python 虚拟机执行的,在本篇文章当中暂时不详细分析。</p></li> <li><p>co_consts,这个字段是一个列表类型的字段,主要是包含一些字符串常量和数值常量,比如上面的 ";<strong>main</strong>" 和 100 。</p></li> <li><p>co_filename,这个字段的含义就是对应的源文件的文件名。</p></li> <li><p>co_firstlineno,这个字段的含义为在 python 源文件当中第一行代码出现的行数,这个字段在进行调试的时候非常重要。</p></li> <li><p>主要含义是标识该 code object 的类型的字段是 co_flags。0x0080 表示这个 block 是一个协程,0x0010 表示这个 code object 是嵌套的等等。</p></li> <li><p>co_lnotab,这个字段的含义主要是用于计算每个字节码指令对应的源代码行数。</p></li> <li><p>The main purpose of the field "co_varnames" is to indicate a name defined locally in a code object.。</p></li> <li><p>co_names,和 co_varnames 相反,表示非本地定义但是在 code object 当中使用的名字。</p></li> <li><p>co_nlocals,这个字段表示在一个 code object 当中本地使用的变量个数。</p></li> <li><p>co_stackszie,因为 python 虚拟机是一个栈式计算机,这个参数的值表示这个栈需要的最大的值。</p></li> <li><p>co_cellvars,co_freevars,这两个字段主要和嵌套函数和函数闭包有关。</p></li> </ul>

    以上がPython 仮想マシンの pyc ファイル構造は何ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

    声明:
    この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。