还有第四种选择:更换print()
global:
printed = []
print = lambda *args: printed.extend(args)
modify()
del print
modified = printed[0]
否则可能会生成修改后的字节码,但这很容易导致导致解释器崩溃的错误(对无效字节码的保护为零),因此请注意。
您可以使用具有更新的字节码的新代码对象来创建新的函数对象;根据您显示的 dis 中的偏移量,我手动创建了新的字节码,该字节码将返回索引 0 处的局部变量:
>>> altered_bytecode = modify.__code__.co_code[:8] + bytes(
... [dis.opmap['LOAD_FAST'], 0, # load local variable 0 onto the stack
... dis.opmap['RETURN_VALUE']])) # and return it.
>>> dis.dis(altered_bytecode)
0 LOAD_GLOBAL 0 (0)
2 LOAD_CONST 1 (1)
4 BINARY_ADD
6 STORE_FAST 0 (0)
8 LOAD_FAST 0 (0)
10 RETURN_VALUE
RETURN_VALUE
返回栈顶的对象;我所做的就是注入一个LOAD_FAST
加载什么的操作码modified
引用到堆栈上。
你必须创建一个新的code
对象,然后是一个新的function
对象包装代码对象,使其可调用:
>>> code = type(modify.__code__)
>>> function = type(modify)
>>> ocode = modify.__code__
>>> new_modify = function(
... code(ocode.co_argcount, ocode.co_kwonlyargcount, ocode.co_nlocals, ocode.co_stacksize,
... ocode.co_flags, altered_bytecode,
... ocode.co_consts, ocode.co_names, ocode.co_varnames, ocode.co_filename,
... 'new_modify', ocode.co_firstlineno, ocode.co_lnotab, ocode.co_freevars,
... ocode.co_cellvars),
... modify.__globals__, 'new_modify', modify.__defaults__, modify.__closure__)
>>> new_modify()
1
显然,这首先需要对 Python 字节码的工作原理有一定的了解;这dis
模块确实包含各种代码的描述,并且dis.opmap字典让您映射回字节值。
有一些模块试图使这变得更容易;看一眼byteplay, the bytecode的模块pwnypack project或其他几个,如果您想进一步探索这一点。
我也衷心推荐您观看玩转 Python 字节码推介会由 Scott Sanderson、Joe Jevnik 在 PyCon 2016 上提供,并使用他们的codetransformer module。非常有趣且信息丰富。