Python の dis モジュールでさまざまな import 文を disassemble する

Python の dis モジュールでさまざまな import 文を disassemble する

2008/09/25 9:34am

Python Reference Manual6.12 The import statement を参考に、さまざまな形式の import 文を dis モジュールで disassemble してみた。

import …

まずは、もっとも単純な import ...

>>> dis.disassemble(compile('import os', '', 'exec'))
  1           0 LOAD_CONST               0 (-1)
              3 LOAD_CONST               1 (None)
              6 IMPORT_NAME              0 (os)
              9 STORE_NAME               0 (os)
             12 LOAD_CONST               1 (None)
             15 RETURN_VALUE

IMPORT_NAMEimport するモジュールが指定されていることが分かる。では、as を使って別名をつけるとどうなるだろうか。

>>> dis.disassemble(compile('import os as os2', '', 'exec'))
  1           0 LOAD_CONST               0 (-1)
              3 LOAD_CONST               1 (None)
              6 IMPORT_NAME              0 (os)
              9 STORE_NAME               1 (os2)
             12 LOAD_CONST               1 (None)
             15 RETURN_VALUE

STORE_NAME の引数が os から os2 に変わった。つまり、登録するシンボル名は STORE_NAME で指定されるということだろう。

import …, …

複数のモジュールを import してみよう。

>>> dis.disassemble(compile('import os, sys', '', 'exec'))
  1           0 LOAD_CONST               0 (-1)
              3 LOAD_CONST               1 (None)
              6 IMPORT_NAME              0 (os)
              9 STORE_NAME               0 (os)
             12 LOAD_CONST               0 (-1)
             15 LOAD_CONST               1 (None)
             18 IMPORT_NAME              1 (sys)
             21 STORE_NAME               1 (sys)
             24 LOAD_CONST               1 (None)
             27 RETURN_VALUE

特別なバイトコードが用意されているわけではなく、引数だけが異なるバイトコードが繰り返されるようだ。

from … import …

モジュールから特定のシンボルだけを import する from ... import ...

>>> dis.disassemble(compile('from os.path import join', '', 'exec'))
  1           0 LOAD_CONST               0 (-1)
              3 LOAD_CONST               1 (('join',))
              6 IMPORT_NAME              0 (os.path)
              9 IMPORT_FROM              1 (join)
             12 STORE_NAME               1 (join)
             15 POP_TOP
             16 LOAD_CONST               2 (None)
             19 RETURN_VALUE

>>> dis.disassemble(compile('from os.path import join, dirname', '', 'exec'))
  1           0 LOAD_CONST               0 (-1)
              3 LOAD_CONST               1 (('join', 'dirname'))
              6 IMPORT_NAME              0 (os.path)
              9 IMPORT_FROM              1 (join)
             12 STORE_NAME               1 (join)
             15 IMPORT_FROM              2 (dirname)
             18 STORE_NAME               2 (dirname)
             21 POP_TOP
             22 LOAD_CONST               2 (None)
             25 RETURN_VALUE

新しく IMPORT_FROM が登場しているが、基本的な構造は変わらない。

すこし特殊なのが from ... import * でモジュール内のすべてのシンボルを import する場合だ。

>>> dis.disassemble(compile('from module import *', '', 'exec'))
  1           0 LOAD_CONST               0 (-1)
              3 LOAD_CONST               1 (('*',))
              6 IMPORT_NAME              0 (module)
              9 IMPORT_STAR
             10 LOAD_CONST               2 (None)
             13 RETURN_VALUE

STORE_NAME がなくなり、専用の IMPORT_STAR が使われている。

Relative Imports

相対インポートはどのように実現されているのだろう?

>>> dis.disassemble(compile('from .. b import B', '', 'exec'))
  1           0 LOAD_CONST               0 (2)
              3 LOAD_CONST               1 (('B',))
              6 IMPORT_NAME              0 (b)
              9 IMPORT_FROM              1 (B)
             12 STORE_NAME               1 (B)
             15 POP_TOP
             16 LOAD_CONST               2 (None)
             19 RETURN_VALUE

一見すると、from b import B から変わったところは見当たらない。しかし、最初の LOAD_CONST に注目してほしい。いままで -1 だった引数が 2 になっている。ここで相対インポートの深さを指定しているわけだ。

from future import …

最後に from __future__ がどのようにコンパイルされるのか見ておこう。

>>> dis.disassemble(compile('from __future__ import absolute_import', '', 'exec'))
  1           0 LOAD_CONST               0 (0)
              3 LOAD_CONST               1 (('absolute_import',))
              6 IMPORT_NAME              0 (__future__)
              9 IMPORT_FROM              1 (absolute_import)
             12 STORE_NAME               1 (absolute_import)
             15 POP_TOP
             16 LOAD_CONST               2 (None)
             19 RETURN_VALUE

バイトコードは普通の import と変わらないように見える。

それもそのはずで、実行時の振る舞いは通常の import と同じなのだ。もちろん、コンパイル時はコンパイラが from __future__ を特別扱いしてセマンティクスのチェックなどを行うわけだが、実行時は標準ライブラリの future.py を import するようになっている

追記が長くなったので、つづきを別の記事として書きました

Reference