9.25 拆解Python字節(jié)碼

2018-02-24 15:27 更新

問題

你想通過將你的代碼反編譯成低級的字節(jié)碼來查看它底層的工作機(jī)制。

解決方案

<span class="pre" style="box-sizing: border-box;">dis</span>?模塊可以被用來輸出任何Python函數(shù)的反編譯結(jié)果。例如:

>>> def countdown(n):
... while n > 0:
...     print('T-minus', n)
...     n -= 1
... print('Blastoff!')
...
>>> import dis
>>> dis.dis(countdown)
...
>>>

討論

當(dāng)你想要知道你的程序底層的運行機(jī)制的時候,<span class="pre" style="box-sizing: border-box;">dis</span>?模塊是很有用的。比如如果你想試著理解性能特征。 被?<span class="pre" style="box-sizing: border-box;">dis()</span>?函數(shù)解析的原始字節(jié)碼如下所示:

>>> countdown.__code__.co_code
b"x'\x00|\x00\x00d\x01\x00k\x04\x00r)\x00t\x00\x00d\x02\x00|\x00\x00\x83
\x02\x00\x01|\x00\x00d\x03\x008}\x00\x00q\x03\x00Wt\x00\x00d\x04\x00\x83
\x01\x00\x01d\x00\x00S"
>>>

如果你想自己解釋這段代碼,你需要使用一些在?<span class="pre" style="box-sizing: border-box;">opcode</span>?模塊中定義的常量。例如:

>>> c = countdown.__code__.co_code
>>> import opcode
>>> opcode.opname[c[0]]
>>> opcode.opname[c[0]]
'SETUP_LOOP'
>>> opcode.opname[c[3]]
'LOAD_FAST'
>>>

奇怪的是,在?<span class="pre" style="box-sizing: border-box;">dis</span>?模塊中并沒有函數(shù)讓你以編程方式很容易的來處理字節(jié)碼。 不過,下面的生成器函數(shù)可以將原始字節(jié)碼序列轉(zhuǎn)換成?<span class="pre" style="box-sizing: border-box;">opcodes</span>?和參數(shù)。

import opcode

def generate_opcodes(codebytes):
    extended_arg = 0
    i = 0
    n = len(codebytes)
    while i < n:
        op = codebytes[i]
        i += 1
        if op >= opcode.HAVE_ARGUMENT:
            oparg = codebytes[i] + codebytes[i+1]*256 + extended_arg
            extended_arg = 0
            i += 2
            if op == opcode.EXTENDED_ARG:
                extended_arg = oparg * 65536
                continue
        else:
            oparg = None
        yield (op, oparg)

使用方法如下:

>>> for op, oparg in generate_opcodes(countdown.__code__.co_code):
...     print(op, opcode.opname[op], oparg)

這種方式很少有人知道,你可以利用它替換任何你想要替換的函數(shù)的原始字節(jié)碼。 下面我們用一個示例來演示整個過程:

>>> def add(x, y):
...     return x + y
...
>>> c = add.__code__
>>> c
<code object add at 0x1007beed0, file "<stdin>", line 1>
>>> c.co_code
b'|\x00\x00|\x01\x00\x17S'
>>>
>>> # Make a completely new code object with bogus byte code
>>> import types
>>> newbytecode = b'xxxxxxx'
>>> nc = types.CodeType(c.co_argcount, c.co_kwonlyargcount,
...     c.co_nlocals, c.co_stacksize, c.co_flags, newbytecode, c.co_consts,
...     c.co_names, c.co_varnames, c.co_filename, c.co_name,
...     c.co_firstlineno, c.co_lnotab)
>>> nc
<code object add at 0x10069fe40, file "<stdin>", line 1>
>>> add.__code__ = nc
>>> add(2,3)
Segmentation fault

你可以像這樣耍大招讓解釋器奔潰。但是,對于編寫更高級優(yōu)化和元編程工具的程序員來講, 他們可能真的需要重寫字節(jié)碼。本節(jié)最后的部分演示了這個是怎樣做到的。你還可以參考另外一個類似的例子:?this code on ActiveState

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號