执行切片的Python字节代码有时会导致“SystemError:unknown opcode”

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了执行切片的Python字节代码有时会导致“SystemError:unknown opcode”相关的知识,希望对你有一定的参考价值。

给定一个从以下3行代码编译的代码对象:

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开始,我可以将代码对象的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'的字节代码,范围从索引812(不包括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 LOAD_CONST               2 ('good stuff')
              2 STORE_NAME               1 (b)

那我错过了什么?

答案

我正在回答我自己的问题,因为我找不到关于这个主题的文档,我花了一些时间来弄清楚我错过了什么,这样可能会使碰巧遇到同样问题的其他人受益。

事实证明,每个代码块都需要返回一个值 - 它不是一个不返回值的选项。如果没有明确的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

以上是关于执行切片的Python字节代码有时会导致“SystemError:unknown opcode”的主要内容,如果未能解决你的问题,请参考以下文章

Objective-C 对数组进行切片会导致索引越界错误

python函数得执行过程

python程序执行原理

Python 代码真的会逐行执行吗? [复制]

Python程序的执行原理

在一个线程中定期发送许多 UDP 数据报有时会导致微包突发