1. ホーム
  2. python

[解決済み] スライスされたPythonのバイトコードを実行すると、"SystemError: unknown opcode "が発生することがある。

2022-02-10 07:44:54

質問

次の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

2行目のバイトコードだけを抽出して実行するにはどうしたらいいのでしょうか。 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

しかし、2行目のバイトコードだけを抽出して実行しようとすると 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 LOAD_CONST               2 ('good stuff')
              2 STORE_NAME               1 (b)

では、何が足りないのか?

解決方法は?

このトピックに関する文書が見つからず、何が足りないのか理解するのに時間がかかったので、自分自身の質問に答えています。

その結果、すべてのコードブロックが 必須 値を返さないという選択肢はないのです。もし、明示的に return ステートメントを使用すると None が暗黙のうちに返されることは、問題に示されている最後の2つのバイトコードで明らかです。

             16 LOAD_CONST               4 (None)
             18 RETURN_VALUE

そこで、インデックスからバイトコードをスライスして 12 の最後の行に対して c = True の末尾の暗黙の戻り値を誤って含んでしまいました。 None 幸いなことに、このコードブロックは値を返すという要件を満たしている。

しかし、indexのバイトコードをスライスしてみると、そうではありませんでした。 8 から 12 の2行目には b = 'good stuff' を返すための最後の2バイトのコードが抜けているので None となり、その結果 SystemError: unknown opcode の例外が発生します。

そこで、これを解決するために必要なことは、最後の2バイトのコード(実際には合計4バイトのため、バイトコードは実際にはPython 3で"word"コードになりました)をスライスに付加することでした。

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