Python の dis モジュールでさまざまな import 文を disassemble する
Python Reference Manual の 6.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_NAME
で import
するモジュールが指定されていることが分かる。では、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 するようになっている。