pyc ファイルは、ソース コードを解釈して実行するときに Python によって生成されるバイトコード ファイルで、ソース コードのコンパイル結果が含まれます。 . および関連するメタデータ情報を使用して、Python がコードをより速くロードして実行できるようにします。
コンパイル言語とは異なり、Python はインタープリタ型言語であり、ソース コードを直接マシン コードにコンパイルして実行することはありません。コードを実行する前に、Python インタープリターはまずソース コードをバイトコードにコンパイルし、次にそのバイトコードを解釈して実行します。 .pyc ファイルは、このプロセス中に生成されるバイトコード ファイルです。
Python インタープリターは、初めて .py ファイルを実行するときに、同じディレクトリに対応する .pyc ファイルを生成します。これにより、次回ファイルが読み込まれるときにより速く実行できるようになります。ソース ファイルが変更されて再ロードされると、インタープリターは .pyc ファイルを再生成して、キャッシュされたバイトコードを更新します。
通常の Python ファイルはコンパイラを通じてバイトコードに変換する必要があり、その後バイトコードが Python 仮想マシンに渡され、Python 仮想マシンがバイトコードを実行します。 。全体的なプロセスは次のとおりです。
#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 ファイルを生成します。
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 です。
import struct import time import binascii fname = "./__pycache__/hello.cpython-310.pyc" f = open(fname, "rb") magic = struct.unpack('<l', 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('<l', moddate)[0])) filesz = struct.unpack('<L', filesz) print("magic %s" % (hex(magic))) print("moddate (%s)" % (modtime)) print("File Size %d" % filesz) f.close()
上記のコードの出力は次のとおりです:
magic 0xa0d0d6f
moddate (Mon Mar 27 15:41:45 2023)ファイル サイズ 32
CodeObjectpyc についてファイル操作の詳細については、Python 標準ライブラリ importlib/_bootstrap_external.py ファイルのソース コードを参照してください。
CPython では、
CODEOBJECT
は、バイトコード、定数、変数、位置パラメータ、キーワード パラメータ、および Python コードのその他の情報を含むオブジェクトです。ファイル名、コード行番号など、コードの実行に使用されるメタデータ。
にコンパイルしてから実行します。コンパイル中に、インタープリターは Python コードをバイトコードに変換し、それを CodeObject
オブジェクトに保存します。その後、そのモジュールまたは関数を呼び出すたびに、インタープリターは
内のバイトコードを使用してコードを実行します。 CodeObject
オブジェクトは不変であり、一度作成すると変更することはできません。これは、Python コードのバイトコードが不変であり、CodeObject
オブジェクトにはこれらのバイトコードが含まれているため、これも不変であるためです。
この記事では、主にコード オブジェクトの主な内容とその機能について簡単に紹介しますが、以降の記事では、コード オブジェクトに対応するソース コードと、対応するフィールドの詳細な機能について詳しく説明します。 <p>现在举一个例子来分析一下 pycdemo.py 的 pyc 文件,pycdemo.py 的源程序如下所示:</p><pre class="brush:py;">if __name__ == &#39;__main__&#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(&#39;<l&#39;, fp.read(4))[0]
print(f"magic number = {hex(magic)}")
bit_field = struct.unpack(&#39;<l&#39;, fp.read(4))[0]
print(f"bit filed = {bit_field}")
t = struct.unpack(&#39;<l&#39;, fp.read(4))[0]
print(f"time = {time.asctime(time.localtime(t))}")
file_size = struct.unpack(&#39;<l&#39;, fp.read(4))[0]
print(f"file size = {file_size}")
def show_code(code, indent=&#39;&#39;):
print ("%scode" % indent)
indent += &#39; &#39;
print ("%sargcount %d" % (indent, code.co_argcount))
print ("%snlocals %d" % (indent, code.co_nlocals))
print ("%sstacksize %d" % (indent, code.co_stacksize))
print ("%sflags %04x" % (indent, code.co_flags))
show_hex("code", code.co_code, indent=indent)
dis.disassemble(code)
print ("%sconsts" % indent)
for const in code.co_consts:
if type(const) == types.CodeType:
show_code(const, indent+&#39; &#39;)
else:
print(" %s%r" % (indent, const))
print("%snames %r" % (indent, code.co_names))
print("%svarnames %r" % (indent, code.co_varnames))
print("%sfreevars %r" % (indent, code.co_freevars))
print("%scellvars %r" % (indent, code.co_cellvars))
print("%sfilename %r" % (indent, code.co_filename))
print("%sname %r" % (indent, code.co_name))
print("%sfirstlineno %d" % (indent, code.co_firstlineno))
show_hex("lnotab", code.co_lnotab, indent=indent)
def show_hex(label, h, indent):
h = binascii.hexlify(h)
if len(h) < 60:
print("%s%s %s" % (indent, label, h))
else:
print("%s%s" % (indent, label))
for i in range(0, len(h), 60):
print("%s %s" % (indent, h[i:i+60]))
if __name__ == &#39;__main__&#39;:
filename = "./__pycache__/pycdemo01.cpython-39.pyc"
with open(filename, "rb") 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&#39;650064006b02721464015a01650265018301010064025300&#39;
3 0 LOAD_NAME 0 (__name__)
2 LOAD_CONST 0 (&#39;__main__&#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
>> 20 LOAD_CONST 2 (None)
22 RETURN_VALUE
consts
&#39;__main__&#39;
100
None
names (&#39;__name__&#39;, &#39;a&#39;, &#39;print&#39;)
varnames ()
freevars ()
cellvars ()
filename &#39;./pycdemo01.py&#39;
name &#39;<module>&#39;
firstlineno 3
lnotab b&#39;08010401&#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 サイトの他の関連記事を参照してください。