gpt4 book ai didi

python - 直接从 Python 中的 CodeType 和 FunctionType 创建函数时的奇怪行为

转载 作者:太空宇宙 更新时间:2023-11-04 01:15:55 27 4
gpt4 key购买 nike

免责声明:我知道我正在做的事情可能永远永远不会在真实程序中完成。

我最近刚刚了解了 python 的 types.CodeTypetypes.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 并分解操作码,还是有更简单的方法?

最佳答案

函数和它们的代码对象是紧密耦合的。参数作为本地人提交,本地人按索引查找:

>>> 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 字节码后的整数,它们是局部数组的索引。重新排列你的参数并没有改变那些字节码索引。

code.co_varnames 列表仅用于内省(introspection)(例如 dis 输出),将索引映射回名称,而不是相反。

你必须对字节码进行手术才能改变它;查看dis module了解更多详情。

如果您使用的是 Python 3.4 或更新版本,则可以使用新的 dis.get_instructions() function遍历信息丰富的 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 参数映射到新索引。

关于python - 直接从 Python 中的 CodeType 和 FunctionType 创建函数时的奇怪行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24764244/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com