您好,登錄后才能下訂單哦!
本篇內容介紹了“python虛擬機pyc文件結構是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
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 文件。
第一部分魔數由兩部分組成:
第一部分 魔術是由一個 2 字節的整數和另外兩個字符回車換行組成的, "\r\n" 也占用兩個字節,一共是四個字節。這個兩個字節的整數在不同的 python 版本還不一樣,比如說在 python3.5 當中這個值為 3351 等值,在 python3.9 當中這個值為 3420,3421,3422,3423,3424等值(在 python 3.9 的小版本)。
第二部分 Bit Field 這個字段的主要作用是為了將來能夠實現復現編譯結果,但是在 python3.9a2 時,這個字段的值還全部是 0 。詳細內容可以參考 PEP552-Deterministic pycs 。這個字段在 python2 和 python3 早期版本并沒有(python3.5 還沒有),在 python3 的后期版本這個字段才出現的。
第三部分 就是整個 py 源文件的大小了。
第四部分 也是整個 pyc 文件當中最重要的一個部分,最后一個部分就是一個 CodeObject 對象序列化之后的數據,我們稍后再來仔細分析一下這個對象相關的數據。
我們現在來具體分析一個 pyc 文件,對應的 python 代碼為:
def f(): x = 1 return 2
pyc 文件的十六進制形式如下所示:
? __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 。
第二部分 Bit Field 為:0x0 。
第三部分最后一次修改日期為:0x642148b9 。
第四部分文件大小為:0x20 字節,也就是說 hello.py 這個文件的大小是 32 字節。
下面是一個小的代碼片段用于讀取 pyc 文件的頭部元信息:
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()
上面的代碼輸出結果如下所示:
bit field = b'00000000'
magic 0xa0d0d6f
moddate (Mon Mar 27 15:41:45 2023)
File Size 32
有關 pyc 文件的詳細操作可以查看 python 標準庫 importlib/_bootstrap_external.py 文件源代碼。
在 CPython 中,CodeObject
是一個對象,它包含了 Python 代碼的字節碼、常量、變量、位置參數、關鍵字參數等信息,以及一些用于運行代碼的元數據,如文件名、代碼行號等。
在 CPython 中,當我們執行一個 Python 模塊或函數時,解釋器會先將其代碼編譯為 CodeObject
,然后再執行。在編譯過程中,解釋器會將 Python 代碼轉換為字節碼,并將其保存在 CodeObject
對象中。此后,每當我們調用該模塊或函數時,解釋器都會使用 CodeObject
中的字節碼來執行代碼。
CodeObject
對象是不可變的,一旦創建就不能被修改。這是因為 Python 代碼的字節碼是不可變的,而 CodeObject
對象包含了這些字節碼,所以也是不可變的。
在本篇文章當中主要介紹 code object 當中主要的內容,以及簡單介紹他們的作用,在后續的文章當中會仔細分析 code object 對應的源代碼以及對應的字段的詳細作用。
現在舉一個例子來分析一下 pycdemo.py 的 pyc 文件,pycdemo.py 的源程序如下所示:
if __name__ == '__main__': a = 100 print(a)
下面的代碼是一個用于加載 pycdemo01.cpython-39.pyc 文件(也就是 hello.py 對應的 pyc 文件)的代碼,使用 marshal 讀取 pyc 文件里面的 code object 。
import marshal import dis import struct import time import types import binascii def print_metadata(fp): magic = struct.unpack('<l', fp.read(4))[0] print(f"magic number = {hex(magic)}") bit_field = struct.unpack('<l', fp.read(4))[0] print(f"bit filed = {bit_field}") t = struct.unpack('<l', fp.read(4))[0] print(f"time = {time.asctime(time.localtime(t))}") file_size = struct.unpack('<l', fp.read(4))[0] print(f"file size = {file_size}") def show_code(code, indent=''): print ("%scode" % indent) indent += ' ' 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+' ') 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__ == '__main__': filename = "./__pycache__/pycdemo01.cpython-39.pyc" with open(filename, "rb") as fp: print_metadata(fp) code_object = marshal.load(fp) show_code(code_object)
執行上面的程序輸出結果如下所示:
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'650064006b02721464015a01650265018301010064025300' 3 0 LOAD_NAME 0 (__name__) 2 LOAD_CONST 0 ('__main__') 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 '__main__' 100 None names ('__name__', 'a', 'print') varnames () freevars () cellvars () filename './pycdemo01.py' name '<module>' firstlineno 3 lnotab b'08010401'
下面是 code object 當中各個字段的作用:
首先需要了解一下代碼塊這個概念,所謂代碼塊就是一個小的 python 代碼,被當做一個小的單元整體執行。在 python 當中常見的代碼塊塊有:函數體、類的定義、一個模塊。
argcount,這個表示一個代碼塊的參數個數,這個參數只對函數體代碼塊有用,因為函數可能會有參數,比如上面的 pycdemo.py 是一個模塊而不是一個函數,因此這個參數對應的值為 0 。
co_code,這個對象的具體內容就是一個字節序列,存儲真實的 python 字節碼,主要是用于 python 虛擬機執行的,在本篇文章當中暫時不詳細分析。
co_consts,這個字段是一個列表類型的字段,主要是包含一些字符串常量和數值常量,比如上面的 ";main" 和 100 。
co_filename,這個字段的含義就是對應的源文件的文件名。
co_firstlineno,這個字段的含義為在 python 源文件當中第一行代碼出現的行數,這個字段在進行調試的時候非常重要。
co_flags,這個字段的主要含義就是標識這個 code object 的類型。0x0080 表示這個 block 是一個協程,0x0010 表示這個 code object 是嵌套的等等。
co_lnotab,這個字段的含義主要是用于計算每個字節碼指令對應的源代碼行數。
co_varnames,這個字段的主要含義是表示在一個 code object 本地定義的一個名字。
co_names,和 co_varnames 相反,表示非本地定義但是在 code object 當中使用的名字。
co_nlocals,這個字段表示在一個 code object 當中本地使用的變量個數。
co_stackszie,因為 python 虛擬機是一個棧式計算機,這個參數的值表示這個棧需要的最大的值。
co_cellvars,co_freevars,這兩個字段主要和嵌套函數和函數閉包有關。
“python虛擬機pyc文件結構是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。