欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入解析PYTHON?虛擬機(jī)令人拍案叫絕的字節(jié)碼設(shè)計(jì)

 更新時(shí)間:2023年04月03日 08:56:00   作者:一無(wú)是處的研究僧  
這篇文章主要為大家介紹了PYTHON虛擬機(jī)中令人拍案叫絕的字節(jié)碼設(shè)計(jì)深入詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

PYTHON 字節(jié)碼設(shè)計(jì)

在本篇文章當(dāng)中主要給大家介紹 cpython 虛擬機(jī)對(duì)于字節(jié)碼的設(shè)計(jì)以及在調(diào)試過(guò)程當(dāng)中一個(gè)比較重要的字段 co_lnotab 的設(shè)計(jì)原理!

一條 python 字節(jié)碼主要有兩部分組成,一部分是操作碼,一部分是這個(gè)操作碼的參數(shù),在 cpython 當(dāng)中只有部分字節(jié)碼有參數(shù),如果對(duì)應(yīng)的字節(jié)碼沒(méi)有參數(shù),那么 oparg 的值就等于 0 ,在 cpython 當(dāng)中 opcode < 90 的指令是沒(méi)有參數(shù)的。

opcode 和 oparg 各占一個(gè)字節(jié),cpython 虛擬機(jī)使用小端方式保存字節(jié)碼。

我們使用下面的代碼片段先了解一下字節(jié)碼的設(shè)計(jì):

import dis
def add(a, b):
    return a + b
if __name__ == '__main__':
    print(add.__code__.co_code)
    print("bytecode: ", list(bytearray(add.__code__.co_code)))
    dis.dis(add)

上面的代碼在 python3.9 的輸出如下所示:

b'|\x00|\x01\x17\x00S\x00'
bytecode:  [124, 0, 124, 1, 23, 0, 83, 0]
  5           0 LOAD_FAST                0 (a)
              2 LOAD_FAST                1 (b)
              4 BINARY_ADD
              6 RETURN_VALUE

首先 需要了解的是 add.code.co_code 是函數(shù) add 的字節(jié)碼,是一個(gè)字節(jié)序列,list(bytearray(add.__code__.co_code)) 是將和這個(gè)序列一個(gè)字節(jié)一個(gè)字節(jié)進(jìn)行分開,并且將其變成 10 進(jìn)制形式。根據(jù)前面我們談到的每一條指令——字節(jié)碼占用 2 個(gè)字節(jié),因此上面的字節(jié)碼有四條指令:

操作碼和對(duì)應(yīng)的操作指令在文末有詳細(xì)的對(duì)應(yīng)表。在上面的代碼當(dāng)中主要使用到了三個(gè)字節(jié)碼指令分別是 124,23 和 83 ,他們對(duì)應(yīng)的操作指令分別為 LOAD_FAST,BINARY_ADD,RETURN_VALUE。他們的含義如下:

  • LOAD_FAST:將 varnames[var_num] 壓入棧頂。
  • BINARY_ADD:從棧中彈出兩個(gè)對(duì)象并且將它們相加的結(jié)果壓入棧頂。
  • RETURN_VALUE:彈出棧頂?shù)脑兀瑢⑵渥鳛楹瘮?shù)的返回值。

首先我們需要知道的是 BINARY_ADD 和 RETURN_VALUE,這兩個(gè)操作指令是沒(méi)有參數(shù)的,因此在這兩個(gè)操作碼之后的參數(shù)都是 0 。

但是 LOAD_FAST 是有參數(shù)的,在上面我們已經(jīng)知道 LOAD_FAST 是將 co-varnames[var_num] 壓入棧,var_num 就是指令 LOAD_FAST 的參數(shù)。在上面的代碼當(dāng)中一共有兩條 LOAD_FAST 指令,分別是將 a 和 b 壓入到棧中,他們?cè)?varnames 當(dāng)中的下標(biāo)分別是 0 和 1,因此他們的操作數(shù)就是 0 和 1 。

字節(jié)碼擴(kuò)展參數(shù)

在上面我們談到的 python 字節(jié)碼操作數(shù)和操作碼各占一個(gè)字節(jié),但是如果 varnames 或者常量表的數(shù)據(jù)的個(gè)數(shù)大于 1 個(gè)字節(jié)的表示范圍的話那么改如何處理呢?

為了解決這個(gè)問(wèn)題,cpython 為字節(jié)碼設(shè)計(jì)的擴(kuò)展參數(shù),比如說(shuō)我們要加載常量表當(dāng)中的下標(biāo)為 66113 的對(duì)象,那么對(duì)應(yīng)的字節(jié)碼如下:

[144, 1, 144, 2, 100, 65]

其中 144 表示 EXTENDED_ARG,他本質(zhì)上不是一個(gè) python 虛擬機(jī)需要執(zhí)行的字節(jié)碼,這個(gè)字段設(shè)計(jì)出來(lái)主要是為了用與計(jì)算擴(kuò)展參數(shù)的。

100 對(duì)應(yīng)的操作指令是 LOAD_CONST ,其操作碼是 65,但是上面的指令并不會(huì)加載常量表當(dāng)中下標(biāo)為 65 對(duì)象,而是會(huì)加載下標(biāo)為 66113 的對(duì)象,原因就是因?yàn)?EXTENDED_ARG 。

現(xiàn)在來(lái)模擬一下上面的分析過(guò)程:

  • 先讀取一條字節(jié)碼指令,操作碼等于 144 ,說(shuō)明是擴(kuò)展參數(shù),那么此時(shí)的參數(shù) arg 就等于 (1 x (1 << 8)) = 256 。
  • 讀取第二條字節(jié)碼指令,操作碼等于 144 ,說(shuō)明是擴(kuò)展參數(shù),因?yàn)榍懊?arg 已經(jīng)存在切不等于 0 了,那么此時(shí) arg 的計(jì)算方式已經(jīng)發(fā)生了改變,arg = arg << 8 + 2 << 8 ,也就是說(shuō)原來(lái)的 arg 乘以 256 再加上新的操作數(shù)乘以 256 ,此時(shí) arg = 66048 。
  • 讀取第三條字節(jié)碼指令,操作碼等于 100,此時(shí)是 LOAD_CONST 這條指令,那么此時(shí)的操作碼等于 arg += 65,因?yàn)椴僮鞔a不是 EXTENDED_ARG 因此操作數(shù)不需要在乘以 256 了。

上面的計(jì)算過(guò)程用程序代碼表示如下,下面的代碼當(dāng)中 code 就是真正的字節(jié)序列 HAVE_ARGUMENT = 90 。

def _unpack_opargs(code):
    extended_arg = 0
    for i in range(0, len(code), 2):
        op = code[i]
        if op >= HAVE_ARGUMENT:
            arg = code[i+1] | extended_arg
            extended_arg = (arg << 8) if op == EXTENDED_ARG else 0
        else:
            arg = None
        yield (i, op, arg)

我們可以使用代碼來(lái)驗(yàn)證我們前面的分析:

import dis
def num_to_byte(n):
    return n.to_bytes(1, "little")
def nums_to_bytes(data):
    ans = b"".join([num_to_byte(n) for n in data])
    return ans
if __name__ == '__main__':
    # extended_arg extended_num opcode oparg for python_version > 3.5
    bytecode = nums_to_bytes([144, 1, 144, 2, 100, 65])
    print(bytecode)
    dis.dis(bytecode)

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

b'\x90\x01\x90\x02dA'
          0 EXTENDED_ARG             1
          2 EXTENDED_ARG           258
          4 LOAD_CONST           66113 (66113)

根據(jù)上面程序的輸出結(jié)果可以看到我們的分析結(jié)果是正確的。

源代碼字節(jié)碼映射表

在本小節(jié)主要分析一個(gè) code object 對(duì)象當(dāng)中的 co_lnotab 字段,通過(guò)分析一個(gè)具體的字段來(lái)學(xué)習(xí)這個(gè)字段的設(shè)計(jì)。

import dis
def add(a, b):
    a += 1
    b += 2
    return a + b
if __name__ == '__main__':
    dis.dis(add.__code__)
    print(f"{list(bytearray(add.__code__.co_lnotab)) = }")
    print(f"{add.__code__.co_firstlineno = }")

首先 dis 的輸出第一列是字節(jié)碼對(duì)應(yīng)的源代碼的行號(hào),第二列是字節(jié)碼在字節(jié)序列當(dāng)中的位移。

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

  源代碼的行號(hào)  字節(jié)碼的位移
  6           0 LOAD_FAST                0 (a)
              2 LOAD_CONST               1 (1)
              4 INPLACE_ADD
              6 STORE_FAST               0 (a)
  7           8 LOAD_FAST                1 (b)
             10 LOAD_CONST               2 (2)
             12 INPLACE_ADD
             14 STORE_FAST               1 (b)
  8          16 LOAD_FAST                0 (a)
             18 LOAD_FAST                1 (b)
             20 BINARY_ADD
             22 RETURN_VALUE
list(bytearray(add.__code__.co_lnotab)) = [0, 1, 8, 1, 8, 1]
add.__code__.co_firstlineno = 5

從上面代碼的輸出結(jié)果可以看出字節(jié)碼一共分成三段,每段表示一行代碼的字節(jié)碼?,F(xiàn)在我們來(lái)分析一下 co_lnotab 這個(gè)字段,這個(gè)字段其實(shí)也是兩個(gè)字節(jié)為一段的。比如上面的 [0, 1, 8, 1, 8, 1] 就可以分成三段 [0, 1], [8, 1], [8, 1] 。這其中的含義分別為:

  • 第一個(gè)數(shù)字表示距離上一行代碼的字節(jié)碼數(shù)目。
  • 第二個(gè)數(shù)字表示距離上一行有效代碼的行數(shù)。

現(xiàn)在我們來(lái)模擬上面代碼的字節(jié)碼的位移和源代碼行數(shù)之間的關(guān)系:

  • [0, 1],說(shuō)明這行代碼離上一行代碼的字節(jié)位移是 0 ,因此我們可以看到使用 dis 輸出的字節(jié)碼 LOAD_FAST ,前面的數(shù)字是 0,距離上一行代碼的行數(shù)等于 1 ,代碼的第一行的行號(hào)等于 5,因此 LOAD_FAST 對(duì)應(yīng)的行號(hào)等于 5 + 1 = 6 。
  • [8, 1],說(shuō)明這行代碼距離上一行代碼的字節(jié)位移為 8 個(gè)字節(jié),因此第二塊的 LOAD_FAST 前面是 8 ,距離上一行代碼的行數(shù)等于 1,因此這個(gè)字節(jié)碼對(duì)應(yīng)的源代碼的行號(hào)等于 6 + 1 = 7。
  • [8, 1],同理可以知道這塊字節(jié)碼對(duì)應(yīng)源代碼的行號(hào)是 8 。

現(xiàn)在有一個(gè)問(wèn)題是當(dāng)兩行代碼之間相距的行數(shù)超過(guò) 一個(gè)字節(jié)的表示范圍怎么辦?在 python3.5 以后如果行數(shù)差距大于 127,那么就使用 (0, 行數(shù)) 對(duì)下一個(gè)組合進(jìn)行表示,(0, x_1), (0, x_2) ... ,直到 x_1 + ... + x_n = 行數(shù)。

在后面的程序當(dāng)中我們會(huì)使用 compile 這個(gè) python 內(nèi)嵌函數(shù)。當(dāng)你使用Python編寫代碼時(shí),可以使用compile()函數(shù)將Python代碼編譯成字節(jié)代碼對(duì)象。這個(gè)字節(jié)碼對(duì)象可以被傳遞給Python的解釋器或虛擬機(jī),以執(zhí)行代碼。

compile()函數(shù)接受三個(gè)參數(shù):

  • source: 要編譯的Python代碼,可以是字符串,字節(jié)碼或AST對(duì)象。
  • filename: 代碼來(lái)源的文件名(如果有),通常為字符串。
  • mode: 編譯代碼的模式??梢允?'exec'、'eval' 或 'single' 中的一個(gè)。'exec' 模式用于編譯多行代碼,'eval' 用于編譯單個(gè)表達(dá)式,'single' 用于編譯單行代碼。
import dis
code = """
x=1
y=2
""" \
+ "\n" * 500 + \
"""
z=x+y
"""
code = compile(code, '<string>', 'exec')
print(list(bytearray(code.co_lnotab)))
print(code.co_firstlineno)
dis.dis(code)

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

[0, 1, 4, 1, 4, 127, 0, 127, 0, 127, 0, 121]
1
  2           0 LOAD_CONST               0 (1)
              2 STORE_NAME               0 (x)

  3           4 LOAD_CONST               1 (2)
              6 STORE_NAME               1 (y)

505           8 LOAD_NAME                0 (x)
             10 LOAD_NAME                1 (y)
             12 BINARY_ADD
             14 STORE_NAME               2 (z)
             16 LOAD_CONST               2 (None)
             18 RETURN_VALUE

根據(jù)我們前面的分析因?yàn)榈谌泻偷诙兄g的差距大于 127 ,因此后面的多個(gè)組合都是用于表示行數(shù)的。

505 = 3(前面已經(jīng)有三行了) + (127 + 127 + 127 + 121)(這個(gè)是第二行和第三行之間的差距,這個(gè)值為 502,中間有 500 個(gè)換行但是因?yàn)樽址嗉拥脑蜻€增加了兩個(gè)換行,因此一共是 502 個(gè)換行)。

具體的算法用代碼表示如下所示,下面的參數(shù)就是我們傳遞給 dis 模塊的 code,也就是一個(gè) code object 對(duì)象。

def findlinestarts(code):
    """Find the offsets in a byte code which are start of lines in the source.
    Generate pairs (offset, lineno) as described in Python/compile.c.
    """
    byte_increments = code.co_lnotab[0::2]
    line_increments = code.co_lnotab[1::2]
    bytecode_len = len(code.co_code)
    lastlineno = None
    lineno = code.co_firstlineno
    addr = 0
    for byte_incr, line_incr in zip(byte_increments, line_increments):
        if byte_incr:
            if lineno != lastlineno:
                yield (addr, lineno)
                lastlineno = lineno
            addr += byte_incr
            if addr >= bytecode_len:
                # The rest of the lnotab byte offsets are past the end of
                # the bytecode, so the lines were optimized away.
                return
        if line_incr >= 0x80:
            # line_increments is an array of 8-bit signed integers
            line_incr -= 0x100
        lineno += line_incr
    if lineno != lastlineno:
        yield (addr, lineno)

PYTHON 字節(jié)碼表

操作操作碼
POP_TOP1
ROT_TWO2
ROT_THREE3
DUP_TOP4
DUP_TOP_TWO5
ROT_FOUR6
NOP9
UNARY_POSITIVE10
UNARY_NEGATIVE11
UNARY_NOT12
UNARY_INVERT15
BINARY_MATRIX_MULTIPLY16
INPLACE_MATRIX_MULTIPLY17
BINARY_POWER19
BINARY_MULTIPLY20
BINARY_MODULO22
BINARY_ADD23
BINARY_SUBTRACT24
BINARY_SUBSCR25
BINARY_FLOOR_DIVIDE26
BINARY_TRUE_DIVIDE27
INPLACE_FLOOR_DIVIDE28
INPLACE_TRUE_DIVIDE29
RERAISE48
WITH_EXCEPT_START49
GET_AITER50
GET_ANEXT51
BEFORE_ASYNC_WITH52
END_ASYNC_FOR54
INPLACE_ADD55
INPLACE_SUBTRACT56
INPLACE_MULTIPLY57
INPLACE_MODULO59
STORE_SUBSCR60
DELETE_SUBSCR61
BINARY_LSHIFT62
BINARY_RSHIFT63
BINARY_AND64
BINARY_XOR65
BINARY_OR66
INPLACE_POWER67
GET_ITER68
GET_YIELD_FROM_ITER69
PRINT_EXPR70
LOAD_BUILD_CLASS71
YIELD_FROM72
GET_AWAITABLE73
LOAD_ASSERTION_ERROR74
INPLACE_LSHIFT75
INPLACE_RSHIFT76
INPLACE_AND77
INPLACE_XOR78
INPLACE_OR79
LIST_TO_TUPLE82
RETURN_VALUE83
IMPORT_STAR84
SETUP_ANNOTATIONS85
YIELD_VALUE86
POP_BLOCK87
POP_EXCEPT89
STORE_NAME90
DELETE_NAME91
UNPACK_SEQUENCE92
FOR_ITER93
UNPACK_EX94
STORE_ATTR95
DELETE_ATTR96
STORE_GLOBAL97
DELETE_GLOBAL98
LOAD_CONST100
LOAD_NAME101
BUILD_TUPLE102
BUILD_LIST103
BUILD_SET104
BUILD_MAP105
LOAD_ATTR106
COMPARE_OP107
IMPORT_NAME108
IMPORT_FROM109
JUMP_FORWARD110
JUMP_IF_FALSE_OR_POP111
JUMP_IF_TRUE_OR_POP112
JUMP_ABSOLUTE113
POP_JUMP_IF_FALSE114
POP_JUMP_IF_TRUE115
LOAD_GLOBAL116
IS_OP117
CONTAINS_OP118
JUMP_IF_NOT_EXC_MATCH121
SETUP_FINALLY122
LOAD_FAST124
STORE_FAST125
DELETE_FAST126
RAISE_VARARGS130
CALL_FUNCTION131
MAKE_FUNCTION132
BUILD_SLICE133
LOAD_CLOSURE135
LOAD_DEREF136
STORE_DEREF137
DELETE_DEREF138
CALL_FUNCTION_KW141
CALL_FUNCTION_EX142
SETUP_WITH143
LIST_APPEND145
SET_ADD146
MAP_ADD147
LOAD_CLASSDEREF148
EXTENDED_ARG144
SETUP_ASYNC_WITH154
FORMAT_VALUE155
BUILD_CONST_KEY_MAP156
BUILD_STRING157
LOAD_METHOD160
CALL_METHOD161
LIST_EXTEND162
SET_UPDATE163
DICT_MERGE164
DICT_UPDATE165

總結(jié)

在本篇文章當(dāng)中主要給大家介紹了 cpython 當(dāng)中對(duì)于字節(jié)碼和源代碼和字節(jié)碼之間的映射關(guān)系的具體設(shè)計(jì),這對(duì)于我們深入去理解 cpython 虛擬機(jī)的設(shè)計(jì)非常有幫助!

本篇文章是深入理解 python 虛擬機(jī)系列文章之一,文章地址:github.com/Chang-LeHun…

更多精彩內(nèi)容合集可訪問(wèn)項(xiàng)目:github.com/Chang-LeHun…

以上就是PYTHON 虛擬機(jī):令人拍案叫絕的字節(jié)碼設(shè)計(jì)的詳細(xì)內(nèi)容,更多關(guān)于PYTHON 虛擬機(jī)字節(jié)碼設(shè)計(jì)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Python調(diào)用C語(yǔ)言程序方法解析

    Python調(diào)用C語(yǔ)言程序方法解析

    這篇文章主要介紹了Python調(diào)用C語(yǔ)言程序方法解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-07-07
  • Python全局變量用法實(shí)例分析

    Python全局變量用法實(shí)例分析

    這篇文章主要介紹了Python全局變量用法,結(jié)合實(shí)例形式分析了Python中全局變量的定義、使用方法與相關(guān)注意事項(xiàng),需要的朋友可以參考下
    2016-07-07
  • Python使用Bokeh進(jìn)行交互式數(shù)據(jù)可視化

    Python使用Bokeh進(jìn)行交互式數(shù)據(jù)可視化

    Bokeh是一個(gè)Python庫(kù),用于在Web瀏覽器中創(chuàng)建交互式數(shù)據(jù)可視化,這篇文章主要為大家學(xué)習(xí)介紹了如何使用Bokeh實(shí)現(xiàn)回執(zhí)交互式數(shù)據(jù)可視化圖表,感興趣的可以學(xué)習(xí)一下
    2023-07-07
  • Python自動(dòng)化測(cè)試selenium指定截圖文件名方法

    Python自動(dòng)化測(cè)試selenium指定截圖文件名方法

    這篇文章主要介紹了Python自動(dòng)化測(cè)試selenium指定截圖文件名方法,Selenium?支持?Web?瀏覽器的自動(dòng)化,它提供一套測(cè)試函數(shù),用于支持?Web?自動(dòng)化測(cè)試,下文基于python實(shí)現(xiàn)指定截圖文件名方法,需要的小伙伴可以參考一下
    2022-05-05
  • Python圖像處理庫(kù)PIL中圖像格式轉(zhuǎn)換的實(shí)現(xiàn)

    Python圖像處理庫(kù)PIL中圖像格式轉(zhuǎn)換的實(shí)現(xiàn)

    這篇文章主要介紹了Python圖像處理庫(kù)PIL中圖像格式轉(zhuǎn)換的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-02-02
  • 自己使用總結(jié)Python程序代碼片段

    自己使用總結(jié)Python程序代碼片段

    這篇文章主要介紹了自己使用總結(jié)Python程序代碼片段,本文收集了如反向讀取文件、往文件中所有添加指定的前綴、匿名函數(shù)作為返回值、將二進(jìn)制數(shù)轉(zhuǎn)為10進(jìn)制數(shù)等實(shí)用代碼片段,需要的朋友可以參考下
    2015-06-06
  • Python深度學(xué)習(xí)實(shí)戰(zhàn)PyQt5窗口切換的堆疊布局示例詳解

    Python深度學(xué)習(xí)實(shí)戰(zhàn)PyQt5窗口切換的堆疊布局示例詳解

    本文以堆疊窗口控件為例,詳細(xì)介紹堆疊布局的界面設(shè)計(jì)和程序?qū)崿F(xiàn)過(guò)程,通過(guò)案例帶小白創(chuàng)建一個(gè)典型的堆疊布局多窗口切換程序
    2021-10-10
  • 使用Python生成跑馬燈視頻的完整代碼

    使用Python生成跑馬燈視頻的完整代碼

    這篇文章主要介紹了如何使用Python生成跑馬燈視頻,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2023-11-11
  • Django celery實(shí)現(xiàn)異步任務(wù)操作,并在后臺(tái)運(yùn)行(守護(hù)進(jìn)程)

    Django celery實(shí)現(xiàn)異步任務(wù)操作,并在后臺(tái)運(yùn)行(守護(hù)進(jìn)程)

    這篇文章主要介紹了Django celery實(shí)現(xiàn)異步任務(wù)操作,并在后臺(tái)運(yùn)行(守護(hù)進(jìn)程),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-03-03
  • Pyorch之numpy與torch之間相互轉(zhuǎn)換方式

    Pyorch之numpy與torch之間相互轉(zhuǎn)換方式

    今天小編就為大家分享一篇Pyorch之numpy與torch之間相互轉(zhuǎn)換方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2019-12-12

最新評(píng)論