Выполнение нарезанных байтовых кодов Python иногда приводит к «SystemError: неизвестный код операции»

#python #python-3.x #bytecode

#python #python-3.x #байт-код

Вопрос:

Учитывая объект code, скомпилированный из следующих 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 атрибут объекта code, который содержит необработанные байтовые коды, из index 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' которая варьируется от index 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) покажет, что новый объект code, по-видимому, содержит правильные байтовые коды для b = 'good stuff' :

   1           0 LOAD_CONST               2 ('good stuff')
              2 STORE_NAME               1 (b)
  

Итак, чего мне не хватает?

Ответ №1:

Я отвечаю на свой собственный вопрос, потому что я не смог найти документации по этой теме, и мне потребовалось некоторое время, чтобы выяснить, чего мне не хватает, чтобы это могло принести пользу другим, которые столкнулись с такой же проблемой.

Оказывается, что для возврата значения требуется каждый блок кода — не возвращать значение не вариант. Если нет явного return оператора, то None будет возвращено неявно, как видно из последних двух байтовых кодов, показанных в вопросе:

              16 LOAD_CONST               4 (None)
             18 RETURN_VALUE
  

Итак, вырезая байтовые коды из index 12 для последней строки c = True , я непреднамеренно включил завершающий неявный возврат None , к счастью, удовлетворяющий требованию, чтобы блок кода возвращал значение.

Такого не было, когда я пытался нарезать байтовые коды из index 8 в 12 для второй строки b = 'good stuff' , поскольку он пропустил последние два байтовых кода для возврата None , тем самым вызывая SystemError: unknown opcode исключение.

Итак, чтобы исправить это, все, что было необходимо, это добавить последние два байтовых кода (фактически всего 4 байта, поскольку байтовые коды фактически стали кодами «word» в 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
  

Комментарии:

1. Со вчерашнего дня без ума от этого, спасибо!