1. 程式人生 > >python位元組碼分析

python位元組碼分析

Python對不可變序列進行重複拼接操作效率會很低,因為每次都會生成一個新的物件,直譯器需要把原來物件中的元素先複製到新的物件裡,然後再追加新的元素。

但是CPython對字串操作進行了優化,因為對字串做+=操作實在是太普遍了。因此,初始化str時會預留出額外的可擴充套件空間,從而進行增量操作的時候不會有複製再追加的這個步驟。

通過位元組碼研究一下這個過程。

>>> s_code = 'a += "b"'
>>> c = compile(s_code, '', 'exec')
>>> c.co_code
b'e\x00\x00d\x00\x007Z\x00\x00d\x01\x00S'
>>> c.co_names ('a',) >>> c.co_consts ('b', None)

得到的位元組碼是Bytes型別的。這裡穿插一些Bytes型別的知識。

Bytes型別

b'e\x00\x00d\x00\x007Z\x00\x00d\x01\x00S',b表示是Bytes型別。Bytes以二進位制位元組序列的形式記錄資料,每一個字元就代表一個位元組(8位)。比如上面的e表示二進位制0110 0101。部分ASCII碼對照表如下圖所示。

但是,不是所有的位元組都是可顯示的,甚至有些位元組無法對應到ASCII碼上(因為ASCII碼只定義了128個字元,而一個位元組有256個)。比如0000 0000對應的ASCII是不可顯示的、0111 1111沒有對應的ASCII碼。

為了表示這些無法顯示的位元組,就引入了\x符號,其表示後續的字元為16進位制。如,\x00表示16進位制的00,也就是二進位制的0000 0000。

至此,所有位元組都可被表示。

位元組碼分析

回到開始的程式碼。為了顯示方便,將b'e\x00\x00d\x00\x007Z\x00\x00d\x01\x00S'轉為16進位制來顯示。

>>> c.co_code.hex()
'650000640000375a000064010053'

通過opcode.opname函式可以得到操作碼所對應的操作指令操作指令對應功能查詢):

>>> import opcode
>>> opcode.opname[0x65
] 'LOAD_NAME'

因此,完整的位元組碼可以解釋為(TOS即top-of-stack,棧頂元素):

位元組:位置,功能
650,LOAD_NAME
0000:引數,將co_names[0]的值,即a的值,壓入棧

643,LOAD_CONST
0000:引數,將co_consts[0],即'b',壓入棧

376,INPLACE_ADD,TOS = TOS1 + TOS

5a7,STORE_NAME
0000:引數,co_names[0]=TOS,即將棧頂賦值給a

6410,LOAD_CONST
0100:引數

5313,RETURN_VALUE,Returns with TOS to the caller of the function

實際上藉助dis函式可以直接獲得可讀的位元組碼:

>>> import dis
>>> dis.dis(s_code)
  1           0 LOAD_NAME                0 (a)
              3 LOAD_CONST               0 ('b')
              6 INPLACE_ADD
              7 STORE_NAME               0 (a)
             10 LOAD_CONST               1 (None)
             13 RETURN_VALUE

完整程式碼:

s_code = 'a += "b"'
c = compile(s_code, '', 'exec')
c.co_code
c.co_names
c.co_consts
c.co_code.hex()
import dis
dis.dis(s_code)

非常失敗,對比了string和tuple的賦值位元組碼,並沒有看出string的優化…

大概是開啟方式不對,留坑待續。