执行切片的Python字节码有时会导致“ SystemError:未知操作码”

时间:2019-03-16 02:51:56

标签: python python-3.x bytecode

给出一个由以下三行代码编译而成的代码对象:

code = compile('''a = 1 / 0 # bad stuff. avoid running this!
b = 'good stuff'
c = True''', '', 'exec')

调用dis.dis(code)会分解为:

  1           0 LOAD_CONST               0 (1)
              2 LOAD_CONST               1 (0)
              4 BINARY_TRUE_DIVIDE
              6 STORE_NAME               0 (a)

  2           8 LOAD_CONST               2 ('good stuff')
             10 STORE_NAME               1 (b)

  3          12 LOAD_CONST               3 (True)
             14 STORE_NAME               2 (c)
             16 LOAD_CONST               4 (None)
             18 RETURN_VALUE

如何仅提取并运行第二行b = 'good stuff'的字节码?

例如,如果我只想提取并运行从字节索引c = True开始的最后一行12的字节码,则可以对代码对象的{{1} }属性,它包含来自索引co_code的原始字节码,以构造一个12对象,然后使用它调用types.CodeType

exec

以便它正确输出分配的import types code3 = types.CodeType( code.co_argcount, code.co_kwonlyargcount, code.co_nlocals, code.co_stacksize, code.co_flags, code.co_code[12:], code.co_consts, code.co_names, code.co_varnames, code.co_filename, code.co_name, code.co_firstlineno, code.co_lnotab, code.co_freevars, code.co_cellvars) exec(code3) print(eval('c')) 的值:

c

但是,如果我尝试仅提取并运行第二行的字节代码True ,则其范围从索引b = 'good stuff'8(不包括12 ):

12

它产生:

code2 = types.CodeType(
    code.co_argcount,
    code.co_kwonlyargcount,
    code.co_nlocals,
    code.co_stacksize,
    code.co_flags,
    code.co_code[8:12],
    code.co_consts,
    code.co_names,
    code.co_varnames,
    code.co_filename,
    code.co_name,
    code.co_firstlineno,
    code.co_lnotab,
    code.co_freevars,
    code.co_cellvars)
exec(code2)
print(eval('b'))

调用XXX lineno: 1, opcode: 0 Traceback (most recent call last): File "/path/file.py", line 21, in <module> exec(code2) File "", line 1, in <module> SystemError: unknown opcode 将表明新代码对象似乎包含dis.dis(code2)的正确字节代码:

b = 'good stuff'

那我想念什么?

1 个答案:

答案 0 :(得分:3)

我正在回答自己的问题,因为我找不到有关该主题的文档,并且花了我一段时间才能弄清我所缺少的内容,因此它可能会使碰巧遇到相同问题的其他人受益。

事实证明,要求每个代码块都必须返回一个值-不选择不返回值是不可行的。如果没有显式的return语句,则None将隐式返回,如问题中显示的最后两个字节代码所示:

             16 LOAD_CONST               4 (None)
             18 RETURN_VALUE

因此,通过将索引12的字节代码切成c = True的最后一行,我无意中包括了None的尾随隐式返回,很幸运地满足了代码块对返回一个值。

当我尝试将8第二行的字节码从索引12切成b = 'good stuff'时,情况并非如此,因为它把最后两个字节码留给了返回None,从而导致SystemError: unknown opcode异常。

要解决此问题,所需要做的就是将最后两个字节代码(实际上总共4个字节,因为字节代码实际上已在Python 3中变成了“单词”代码)附加到切片:

code2 = types.CodeType(
    code.co_argcount,
    code.co_kwonlyargcount,
    code.co_nlocals,
    code.co_stacksize,
    code.co_flags,
    code.co_code[8:12] + code.co_code[-4:],
    code.co_consts,
    code.co_names,
    code.co_varnames,
    code.co_filename,
    code.co_name,
    code.co_firstlineno,
    code.co_lnotab,
    code.co_freevars,
    code.co_cellvars)
exec(code2)
print(eval('b'))

这将正确输出:

good stuff