2017-06-23 44 views
9

當天早些時候,我和文檔字符串和dis模塊大量實驗,並橫跨東西,我似乎無法找到答案來了。字節代碼不同基於它是如何被編譯

首先,我創建了一個文件test.py,內容如下:

def foo(): 
    pass 

就在今年,而不是其他。

然後我打開一個解釋器來觀察程序的字節碼。你可以這樣說:

code = compile(open('test.py').read(), '', 'exec') 

第一個參數是一個字符串形式的代碼,第二個是用於調試(保留爲空是O.K.),而第三個是模式。我已經嘗試了singleexec。結果是一樣的。

在此之後,你可以用dis反編譯的字節碼。

>>> import dis 
>>> dis.dis(code) 

字節碼輸出是這樣的:

1   0 LOAD_CONST    0 (<code object foo at 0x10a25e8b0, file "", line 1>) 
       3 MAKE_FUNCTION   0 
       6 STORE_NAME    0 (foo) 
       9 LOAD_CONST    1 (None) 
      12 RETURN_VALUE   

合理,對於這樣一個簡單的腳本。而且它也有意義。

然後我試圖編譯它通過命令行是這樣的:

$ python -m py_compile test.py 

這導致產生和放置在test.pyc文件中的字節碼。內容可以再次與拆解:

>>> import dis 
>>> dis.dis(open('test.pyc').read()) 

這是輸出:

>> 0 ROT_THREE  
     1 <243>   2573 
>> 4 <157>   19800 
>> 7 BUILD_CLASS  
     8 DUP_TOPX   0 
    11 STOP_CODE  
    12 STOP_CODE  
>> 13 STOP_CODE  
    14 STOP_CODE  
    15 STOP_CODE  
    16 STOP_CODE  
    17 POP_TOP   
    18 STOP_CODE  
    19 STOP_CODE  
    20 STOP_CODE  
    21 BINARY_AND  
    22 STOP_CODE  
    23 STOP_CODE  
    24 STOP_CODE  
    25 POP_JUMP_IF_TRUE 13 
    28 STOP_CODE  
    29 STOP_CODE  
    30 LOAD_CONST   0 (0) 
    33 MAKE_FUNCTION  0 
    36 STORE_NAME   0 (0) 
    39 LOAD_CONST   1 (1) 
    42 RETURN_VALUE 
    43 STORE_SLICE+0 
    44 ROT_TWO   
    45 STOP_CODE  
    46 STOP_CODE  
    47 STOP_CODE  
    48 DUP_TOPX   0 
    51 STOP_CODE  
    52 STOP_CODE  
    53 STOP_CODE  
    54 STOP_CODE  
    55 STOP_CODE  
    56 STOP_CODE  
    57 POP_TOP   
    58 STOP_CODE  
    59 STOP_CODE  
    60 STOP_CODE  
    61 INPLACE_POWER 
    62 STOP_CODE  
    63 STOP_CODE  
    64 STOP_CODE  
    65 POP_JUMP_IF_TRUE  4 
    68 STOP_CODE  
    69 STOP_CODE  
    70 LOAD_CONST   0 (0) 
    73 RETURN_VALUE 
    74 STORE_SLICE+0 
    75 POP_TOP   
    76 STOP_CODE  
    77 STOP_CODE  
    78 STOP_CODE  
    79 INPLACE_XOR  
    80 STORE_SLICE+0 
    81 STOP_CODE  
    82 STOP_CODE  
    83 STOP_CODE  
    84 STOP_CODE  
    85 STORE_SLICE+0 
    86 STOP_CODE  
    87 STOP_CODE  
    88 STOP_CODE  
    89 STOP_CODE  
    90 STORE_SLICE+0 
    91 STOP_CODE  
    92 STOP_CODE  
    93 STOP_CODE  
    94 STOP_CODE  
    95 STORE_SLICE+0 
    96 STOP_CODE  
    97 STOP_CODE  
    98 STOP_CODE  
    99 STOP_CODE  
    100 POP_JUMP_IF_TRUE  7 
    103 STOP_CODE  
    104 STOP_CODE  
    105 LOAD_GLOBAL  29541 (29541) 
    108 LOAD_GLOBAL  28718 (28718) 
    111 SETUP_EXCEPT  884 (to 998) 
    114 STOP_CODE  
    115 STOP_CODE  
    116 STOP_CODE  
    117 BUILD_TUPLE  28527 
    120 POP_TOP   
    121 STOP_CODE  
    122 STOP_CODE  
    123 STOP_CODE  
    124 POP_JUMP_IF_TRUE  2 
    127 STOP_CODE  
    128 STOP_CODE  
    129 STOP_CODE  
    130 POP_TOP   
    131 INPLACE_XOR  
    132 STORE_SLICE+0 
    133 POP_TOP   
    134 STOP_CODE  
    135 STOP_CODE  
    136 STOP_CODE  
    137 LOAD_LOCALS  
    138 STOP_CODE  
    139 STOP_CODE  
    140 STOP_CODE  
    141 STOP_CODE  
    142 STORE_SLICE+0 
    143 STOP_CODE  
    144 STOP_CODE  
    145 STOP_CODE  
    146 STOP_CODE  
    147 STORE_SLICE+0 
    148 STOP_CODE  
    149 STOP_CODE  
    150 STOP_CODE  
    151 STOP_CODE  
    152 STORE_SLICE+0 
    153 STOP_CODE  
    154 STOP_CODE  
    155 STOP_CODE  
    156 STOP_CODE  
    157 POP_JUMP_IF_TRUE  7 
    160 STOP_CODE  
    161 STOP_CODE  
    162 LOAD_GLOBAL  29541 (29541) 
    165 LOAD_GLOBAL  28718 (28718) 
    168 SETUP_EXCEPT  2164 (to 2335) 
    171 STOP_CODE  
    172 STOP_CODE  
    173 STOP_CODE  
    174 STORE_SUBSCR 
    175 IMPORT_FROM  25711 (25711) 
    178 <117>   25964 
    181 BINARY_LSHIFT 
    182 POP_TOP   
    183 STOP_CODE  
    184 STOP_CODE  
    185 STOP_CODE  
    186 POP_JUMP_IF_TRUE  0 
    189 STOP_CODE  
    190 STOP_CODE  

的差別是驚人的。爲什麼字節代碼中存在如此明顯的對比,取決於編譯的方式?

回答

15

一個.pyc文件的內容是不是原始的Python字節碼指令。甲.pyc文件contains

  1. 4字節的幻數,
  2. 一個4字節的修改時間戳,和
  3. 一個編組碼對象

你基本上只是垃圾拆解第二次。

如果你想從一個.pyc拆卸代碼,你可以跳過8個字節,解組代碼的對象,然後調用dis.dis代碼對象:

import dis 
import marshal 

with open('test.pyc', 'b') as f: 
    f.seek(8) 
    dis.dis(marshal.load(f)) 

注意,.pyc格式是自由從版本更改到版本,所以這可能不總是工作。事實上,自引用文章的時間以來它已經發生了變化;他們在Python 3.3中的源文件大小的時間戳之後添加了4個字節,所以在3.3和更高版本中,您必須跳過12個字節。

+0

哇......這個「垃圾」如何轉化爲這種看起來合法的字節碼?我應該問這裏...如果py_compile不編譯腳本...它在做什麼? –

+0

因爲拆卸只需要二進制數據並將其解釋爲它是有效的字節代碼。例如,它將一個零字節解釋爲'STOP_CODE',而不管零字節意圖表示什麼。 – chepner

+3

@折速:這看起來很合理,因爲你不熟悉普通字節碼的外觀。例如,'STOP_CODE'不會出現在正常的字節碼中。至於'py_compile'的作用,它編譯Python文件,但是它將它們編譯爲'.pyc'文件而不是代碼對象。 – user2357112