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 的代码,最终重新构造出新的函数。