Python對不可變序列進行重復拼接操作效率會很低,因為每次都會生成一個新的對象,解釋器需要把原來對象中的元素先復制到新的對象里,然后再追加新的元素。
但是CPython對字符串操作進行了優化,因為對字符串做+=操作實在是太普遍了。因此,初始化str時會預留出額外的可擴展空間,從而進行增量操作的時候不會有復制再追加的這個步驟。
通過字節碼研究一下這個過程。
>>> s_code = 'a += "b"'>>> c = compile(s_code, '', 'exec')>>> c.co_codeb'e/x00/x00d/x00/x007Z/x00/x00d/x01/x00S'>>> c.co_names('a',)>>> c.co_consts('b', None)得到的字節碼是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,棧頂元素):
字節:位置,功能65:0,LOAD_NAME0000:參數,將co_names[0]的值,即a的值,壓入棧64:3,LOAD_CONST0000:參數,將co_consts[0],即'b',壓入棧37:6,INPLACE_ADD,TOS = TOS1 + TOS5a:7,STORE_NAME0000:參數,co_names[0]=TOS,即將棧頂賦值給a64:10,LOAD_CONST0100:參數53:13,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_codec.co_namesc.co_constsc.co_code.hex()import disdis.dis(s_code)
新聞熱點
疑難解答