[英]Strange behaviour when creating a function directly from CodeType and FunctionType in python
免责声明:我知道我在做什么也许应该永远可以在真正的程序来完成。
我最近才了解python的types.CodeType
和types.FunctionType
,它使我对通过这些类手动创建函数感兴趣。 因此,作为一个小测试,我从一个看起来像这样的函数开始:
def x(e, a, b=0, *c, d=0, **f):
print(a, b, c, d, e, f)
并想看看是否可以移动参数以将其转换为:
def x(a, b=0, *c, d=0, e=0, **f):
print(a, b, c, d, e, f)
本质上,我想将普通参数设为仅关键字的参数。 这是我用来做这种突变的代码:
from types import CodeType, FunctionType
def x(e, a, b=0, *c, d=0, **f):
print(a, b, c, d, e, f)
code = x.__code__
codeobj = CodeType(
code.co_argcount - 1, code.co_kwonlyargcount + 1, code.co_nlocals, code.co_stacksize,
code.co_flags, code.co_code, code.co_consts, code.co_names, ('a', 'b', 'd', 'e', 'c', 'f'),
code.co_filename, code.co_name, code.co_firstlineno, code.co_lnotab, code.co_freevars,
code.co_cellvars
)
new_func = FunctionType(codeobj, x.__globals__, x.__name__, x.__defaults__, x.__closure__)
new_func.__kwdefaults__ = {'d': 0, 'e': 0}
奇怪的是,工具提示似乎正确显示(当您开始键入函数调用时,在IDLE解释器中显示的文本的黄色小矩形),它显示“ a,b = 0,* c,d = 0,e = 0,** f“。 但是,函数的行为至少可以说很有趣:
>>> new_func(1)
0 0 () 0 1 {}
>>> new_func(1, 2)
2 0 () 0 1 {}
第一个参数仍以e
,第二个元素仍以a
发送。
有没有办法解决这个问题? 如果存在,是否需要深入研究code.co_code
并拆分操作码,还是有一个更简单的方法?
函数及其代码对象紧密耦合。 参数以本地人的形式提交,并通过index查找本地人:
>>> import dis
>>> def x(e, a, b=0, *c, d=0, **f):
... print(a, b, c, d, e, f)
...
>>> dis.dis(x)
2 0 LOAD_GLOBAL 0 (print)
3 LOAD_FAST 1 (a)
6 LOAD_FAST 2 (b)
9 LOAD_FAST 4 (c)
12 LOAD_FAST 3 (d)
15 LOAD_FAST 0 (e)
18 LOAD_FAST 5 (f)
21 CALL_FUNCTION 6 (6 positional, 0 keyword pair)
24 POP_TOP
25 LOAD_CONST 0 (None)
28 RETURN_VALUE
注意LOAD_FAST
字节码后的整数,它们是locals数组的索引。 重新排列您的参数并没有改变那些字节码索引。
code.co_varnames
列表仅用于自省(例如dis
输出),用于将索引映射回名称,而不能code.co_varnames
。
您必须对字节码进行手术才能改变它。 有关更多详细信息,请参见dis
模块 。
如果您使用Python 3.4或更新版本,您可以利用新的dis.get_instructions()
函数来遍历的信息丰富的序列Instruction
的对象,这应该使这种手术是可行的。 查找LOAD_FAST
指令,并在生成新的字节码时映射索引。
Instruction
对象还没有将它们转换回字节的方法。 加一个是微不足道的:
from dis import Instruction, HAVE_ARGUMENT
def to_bytes(self):
res = bytes([self.opcode])
if self.opcode >= HAVE_ARGUMENT:
res += (self.arg or 0).to_bytes(2, byteorder='little')
return res
Instruction.to_bytes = to_bytes
演示:
>>> [ins.to_bytes() for ins in dis.get_instructions(code)]
[b't\x00\x00', b'|\x01\x00', b'|\x02\x00', b'|\x04\x00', b'|\x03\x00', b'|\x00\x00', b'|\x05\x00', b'\x83\x06\x00', b'\x01', b'd\x00\x00', b'S']
>>> b''.join([ins.to_bytes() for ins in dis.get_instructions(code)]) == code.co_code
True
现在,您要做的就是将.opname == 'LOAD_FAST'
的指令的.arg
参数映射到新索引。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.