问题描述
我构建了一个名为 foo
的函数,用于在字节码级别更改函数的代码并在返回常规函数执行流程之前执行它。
import sys
from types import CodeType
def foo():
frame = sys._getframe(1) # get main's frame
main_code: CodeType = do_something(frame.f_code) # modify function code
# copy globals & locals
main_globals: dict = frame.f_globals.copy()
main_locals: dict = frame.f_locals.copy()
# execute altered bytecode before returning to regular code
exec(main_code,main_globals,main_locals)
return
def main():
bar: list = []
# run altered code
foo()
# return to regular code
bar.append(0)
return bar
if __name__ == '__main__':
main()
虽然在exec
期间对局部变量求值有问题:
Traceback (most recent call last):
File "C:\Users\Pedro\main.py",line 31,in <module>
main()
File "C:\Users\Pedro\main.py",line 23,in main
foo()
File "C:\Users\Pedro\main.py",line 15,in foo
exec(main_code,main_locals)
File "C:\Users\Pedro\main.py",line 26,in main
bar.append(0)
UnboundLocalError: local variable 'bar' referenced before assignment
如果我在调用 main_locals
之前打印 exec
,它显示的内容与调用 foo
之前完成的内容完全相同。我想知道它是否与传递给 frame.f_code.co_*
构造函数的任何 CodeType
参数有关。它们几乎相同,除了实际的字节码 frame.f_code.co_code
,我对其进行了一些操作。
我需要帮助理解为什么在这些全局变量和局部变量下的代码评估未能引用 main
的局部变量。
注意:我很确定对 main
的字节码所做的更改可以防止进程进入不必要的递归。
编辑:正如评论中所要求的,可以恢复 do_something
的基本行为以在调用 main
之前删除所有 foo
的代码.一些额外的步骤将涉及对局部变量应用更改,即 bar
。
import copy
import dis
## dump opcodes into global scope
globals().update(dis.opmap)
NULL = 0
def do_something(f_code) -> CodeType:
bytecode = f_code.co_code
f_consts = copy.deepcopy(f_code.co_consts)
for i in range(0,len(bytecode),2):
cmd,arg = bytecode[i],bytecode[i+1]
# watch for the first occurence of calling 'foo'
if cmd == LOAD_GLOBAL and f_code.co_names[arg] == 'foo':
break # use 'i' variable later
else:
raise NameError('foo is not defined.')
f_bytelist = list(bytecode)
f_bytelist[i:i+4] = [
nop,NULL,## LOAD
LOAD_CONST,len(f_consts) ## CALL
# Constant 'None' will be added to 'f_consts'
]
f_bytelist[-2:] = [nop,NULL] # 'main' function RETURN
# This piece of code removes all code before
# calling 'foo' (except for JUMP_ABSOLUTE) so
# it can be usend inside while loops.
null_code = [True] * i
j = i + 2
while j < len(f_bytelist):
if j >= i:
cmd,arg = f_bytelist[j],f_bytelist[j+1]
if cmd == JUMP_ABSOLUTE and arg < i and null_code[arg]:
j = arg
else:
j += 2
else:
null_code[j] = False
j += 2
else:
for j in range(0,i,2):
if null_code[j]:
f_bytelist[j:j+2] = [nop,NULL] # skip instruction
else:
continue
f_bytecode = bytes(f_bytelist)
f_consts = f_consts + (None,) ## Add constant to return
return CodeType(
f_code.co_argcount,f_code.co_kwonlyargcount,f_code.co_posonlyargcount,# Remove this if Python < 3.8
f_code.co_nlocals,f_code.co_stacksize,f_code.co_flags,f_bytecode,f_consts,f_code.co_names,f_code.co_varnames,f_code.co_filename,f_code.co_name,f_code.co_firstlineno,f_code.co_lnotab,f_code.co_freevars,f_code.co_cellvars
)
解决方法
暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!
如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。
小编邮箱:dio#foxmail.com (将#修改为@)