详解Python 一窥字节码的究竟

  • Post category:Python

Python 字节码简介

Python 是一种解释型语言,执行代码的过程是将 Python 代码翻译成中间代码(Bytecode),再由 Python 虚拟机(Virtual Machine)将其解释执行。在这个过程中,中间代码扮演着核心的角色。Python 中间代码也被称为 Python 字节码。字节码在 Python 执行过程中可以被访问和修改。这使得我们可以通过直接修改字节码来实现一些高级的功能,比如代码行变更、闭包等。

字节码的生成

为了生成 Python 字节码,我们可以用 Python 解释器内置的 compile 函数。如果想查看生成的字节码,可以使用 dis 库的 dis 函数。下面是一个简单的例子:

def add(a, b):
    return a + b

code = compile(add.__code__, filename='', mode='exec')
import dis
dis.dis(code)

运行以上代码,得到的输出如下:

  2           0 LOAD_FAST                0 (a)
              2 LOAD_FAST                1 (b)
              4 BINARY_ADD          
              6 RETURN_VALUE        

可以看到,在函数中,我们用到了 LOAD_FAST、BINARY_ADD、RETURN_VALUE 等字节码指令。这些字节码指令由 Python 虚拟机解释执行,最终实现了 add 函数的功能。

修改字节码

了解了字节码的生成过程后,我们再来看一下如何修改字节码。下面是一个简单的例子。

def foo():
    a = 1
    def bar():
        return a
    return bar

code = compile(foo.__code__, filename='', mode='exec')
import dis
dis.dis(code)

执行以上代码后,输出的字节码如下:

  2           0 LOAD_CONST               1 (1)
              2 STORE_FAST               0 (a)

  3           4 LOAD_CLOSURE             0 (a)
              6 BUILD_TUPLE              1
              8 LOAD_CONST               2 (<code object bar at 0x10c4b8d20, file "", line 3>)
             10 LOAD_CONST               3 ('foo.<locals>.bar')
             12 MAKE_FUNCTION            8 (closure)
             14 STORE_FAST               1 (bar)

  5          16 LOAD_FAST                1 (bar)
             18 RETURN_VALUE

可以看到,bar 函数引用了外部的变量 a。这时我们如果希望令 bar 函数返回 2,可以通过修改字节码实现:

code = compile(foo.__code__, filename='', mode='exec')
code_obj = code.co_consts[2]
new_code = b'\x64\x00\x00' + code_obj.co_code[3:]  # 将 BINARY_ADD 修改为 LOAD_CONST 2
code = type(code_obj)(code_obj.co_argcount, code_obj.co_kwonlyargcount, code_obj.co_nlocals, code_obj.co_stacksize, code_obj.co_flags, new_code, code_obj.co_consts, code_obj.co_names, code_obj.co_varnames, code_obj.co_filename, code_obj.co_name, code_obj.co_firstlineno, code_obj.co_lnotab, code_obj.co_freevars, code_obj.co_cellvars)
import types
new_func = types.FunctionType(code, globals())
print(new_func()())  # 输出 2

这里,我们首先获取了原函数的字节码对象,然后将其修改为令 bar 函数返回 2 的代码,最终重新构造出新的函数。