溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

python虛擬機pyc文件結(jié)構(gòu)是什么

發(fā)布時間:2023-05-10 15:28:59 來源:億速云 閱讀:90 作者:zzz 欄目:開發(fā)技術(shù)

本篇內(nèi)容介紹了“python虛擬機pyc文件結(jié)構(gòu)是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠?qū)W有所成!

    PYC 文件

    pyc 文件是 Python 在解釋執(zhí)行源代碼時生成的一種字節(jié)碼文件,它包含了源代碼的編譯結(jié)果和相關的元數(shù)據(jù)信息,以便于 Python 可以更快地加載和執(zhí)行代碼。

    Python 是一種解釋型語言,它不像編譯型語言那樣將源代碼直接編譯成機器碼執(zhí)行。Python 的解釋器會在運行代碼之前先將源代碼編譯成字節(jié)碼,然后將字節(jié)碼解釋執(zhí)行。.pyc 文件就是這個過程中生成的字節(jié)碼文件。

    當 Python 解釋器首次執(zhí)行一個 .py 文件時,它會在同一目錄下生成一個對應的 .pyc 文件,以便于下次加載該文件時可以更快地執(zhí)行。如果源文件在修改之后被重新加載,解釋器會重新生成 .pyc 文件以更新緩存的字節(jié)碼。

    生成 PYC 文件

    正常的 python 文件需要通過編譯器變成字節(jié)碼,然后將字節(jié)碼交給 python 虛擬機,然后 python 虛擬機會執(zhí)行字節(jié)碼。整體流程如下所示:

    python虛擬機pyc文件結(jié)構(gòu)是什么

    我們可以直接使用 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文件結(jié)構(gòu)是什么

    第一部分魔數(shù)由兩部分組成:

    python虛擬機pyc文件結(jié)構(gòu)是什么

    第一部分 魔術(shù)是由一個 2 字節(jié)的整數(shù)和另外兩個字符回車換行組成的, "\r\n" 也占用兩個字節(jié),一共是四個字節(jié)。這個兩個字節(jié)的整數(shù)在不同的 python 版本還不一樣,比如說在 python3.5 當中這個值為 3351 等值,在 python3.9 當中這個值為 3420,3421,3422,3423,3424等值(在 python 3.9 的小版本)。

    第二部分 Bit Field 這個字段的主要作用是為了將來能夠?qū)崿F(xiàn)復現(xiàn)編譯結(jié)果,但是在 python3.9a2 時,這個字段的值還全部是 0 。詳細內(nèi)容可以參考 PEP552-Deterministic pycs 。這個字段在 python2 和 python3 早期版本并沒有(python3.5 還沒有),在 python3 的后期版本這個字段才出現(xiàn)的。

    第三部分 就是整個 py 源文件的大小了。

    第四部分 也是整個 pyc 文件當中最重要的一個部分,最后一個部分就是一個 CodeObject 對象序列化之后的數(shù)據(jù),我們稍后再來仔細分析一下這個對象相關的數(shù)據(jù)。

    我們現(xiàn)在來具體分析一個 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

    因為數(shù)據(jù)使用小端表示方式,因此對于上面的數(shù)據(jù)來說:

    • 第一部分魔數(shù)為:0xa0d0d6f 。

    • 第二部分 Bit Field 為:0x0 。

    • 第三部分最后一次修改日期為:0x642148b9 。

    • 第四部分文件大小為:0x20 字節(jié),也就是說 hello.py 這個文件的大小是 32 字節(jié)。

    下面是一個小的代碼片段用于讀取 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()

    上面的代碼輸出結(jié)果如下所示:

    bit field = b'00000000'
    magic 0xa0d0d6f
    moddate (Mon Mar 27 15:41:45 2023)
    File Size 32

    有關 pyc 文件的詳細操作可以查看 python 標準庫 importlib/_bootstrap_external.py 文件源代碼。

    CODEOBJECT

    在 CPython 中,CodeObject 是一個對象,它包含了 Python 代碼的字節(jié)碼、常量、變量、位置參數(shù)、關鍵字參數(shù)等信息,以及一些用于運行代碼的元數(shù)據(jù),如文件名、代碼行號等。

    在 CPython 中,當我們執(zhí)行一個 Python 模塊或函數(shù)時,解釋器會先將其代碼編譯為 CodeObject,然后再執(zhí)行。在編譯過程中,解釋器會將 Python 代碼轉(zhuǎn)換為字節(jié)碼,并將其保存在 CodeObject 對象中。此后,每當我們調(diào)用該模塊或函數(shù)時,解釋器都會使用 CodeObject 中的字節(jié)碼來執(zhí)行代碼。

    CodeObject 對象是不可變的,一旦創(chuàng)建就不能被修改。這是因為 Python 代碼的字節(jié)碼是不可變的,而 CodeObject 對象包含了這些字節(jié)碼,所以也是不可變的。

    在本篇文章當中主要介紹 code object 當中主要的內(nèi)容,以及簡單介紹他們的作用,在后續(xù)的文章當中會仔細分析 code object 對應的源代碼以及對應的字段的詳細作用。

    現(xiàn)在舉一個例子來分析一下 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)

    執(zhí)行上面的程序輸出結(jié)果如下所示:

    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 代碼,被當做一個小的單元整體執(zhí)行。在 python 當中常見的代碼塊塊有:函數(shù)體、類的定義、一個模塊。

    • argcount,這個表示一個代碼塊的參數(shù)個數(shù),這個參數(shù)只對函數(shù)體代碼塊有用,因為函數(shù)可能會有參數(shù),比如上面的 pycdemo.py 是一個模塊而不是一個函數(shù),因此這個參數(shù)對應的值為 0 。

    • co_code,這個對象的具體內(nèi)容就是一個字節(jié)序列,存儲真實的 python 字節(jié)碼,主要是用于 python 虛擬機執(zhí)行的,在本篇文章當中暫時不詳細分析。

    • co_consts,這個字段是一個列表類型的字段,主要是包含一些字符串常量和數(shù)值常量,比如上面的 ";main" 和 100 。

    • co_filename,這個字段的含義就是對應的源文件的文件名。

    • co_firstlineno,這個字段的含義為在 python 源文件當中第一行代碼出現(xiàn)的行數(shù),這個字段在進行調(diào)試的時候非常重要。

    • co_flags,這個字段的主要含義就是標識這個 code object 的類型。0x0080 表示這個 block 是一個協(xié)程,0x0010 表示這個 code object 是嵌套的等等。

    • co_lnotab,這個字段的含義主要是用于計算每個字節(jié)碼指令對應的源代碼行數(shù)。

    • co_varnames,這個字段的主要含義是表示在一個 code object 本地定義的一個名字。

    • co_names,和 co_varnames 相反,表示非本地定義但是在 code object 當中使用的名字。

    • co_nlocals,這個字段表示在一個 code object 當中本地使用的變量個數(shù)。

    • co_stackszie,因為 python 虛擬機是一個棧式計算機,這個參數(shù)的值表示這個棧需要的最大的值。

    • co_cellvars,co_freevars,這兩個字段主要和嵌套函數(shù)和函數(shù)閉包有關。

    “python虛擬機pyc文件結(jié)構(gòu)是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關的知識可以關注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!

    向AI問一下細節(jié)

    免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI