簡體   English   中英

直接從python中的CodeType和FunctionType創建函數時的奇怪行為

[英]Strange behaviour when creating a function directly from CodeType and FunctionType in python

免責聲明:我知道我在做什么也許應該永遠可以在真正的程序來完成。

我最近才了解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並拆分操作碼,還是有一個更簡單的方法?

函數及其代碼對象緊密耦合。 參數以本地人的形式提交,並通過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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM