Python の dis モジュールでさまざまな import 文を disassemble する (2)
前回の記事への追記が長くなりそうなので、新しい記事にまとめました。
import と import as
import <module>
と import <module> as <name>
では、どのようなバイトコードにコンパイルされるか、つまり、実行時の振る舞いが微妙に異なることに注意しよう。
>>> dis.disassemble(compile('import os.path', '', 'exec'))
1 0 LOAD_CONST 0 (-1)
3 LOAD_CONST 1 (None)
6 IMPORT_NAME 0 (os.path)
9 STORE_NAME 1 (os)
12 LOAD_CONST 1 (None)
15 RETURN_VALUE
>>> dis.disassemble(compile('import os.path as os_path', '', 'exec'))
1 0 LOAD_CONST 0 (-1)
3 LOAD_CONST 1 (None)
6 IMPORT_NAME 0 (os.path)
9 LOAD_ATTR 1 (path)
12 STORE_NAME 2 (os_path)
15 LOAD_CONST 1 (None)
18 RETURN_VALUE
前者では IMPORT_NAME os.path
でロードされたモジュールを os
という名前でシンボルテーブルに登録する。では、IMPORT_NAME os.path
でロードされるモジュールは何かといえば、これは os モジュールである。os.path ではない。
そして、os モジュール内部では os.path モジュールを path
という名前でインポートしている(実際には、プラットフォームごとに異なるモジュールをインポートする)。そのため、os モジュールを os
という名前でシンボルテーブルに登録しておけば、os.path
で os.path モジュールを参照できるわけだ(このへんは次節で更に掘り下げる)。
それにたいして、後者の import os.path as os_path
では IMPORT_NAME os.path
でロードされたモジュールから LOAD_ATTR path
で取り出した os.path モジュールを os_path
という名前でシンボルテーブルに登録する。このように、import <module>
と import <module> as <name>
では、実行時の振る舞いが微妙に異なっている。
import A と import A.B
ここからはバイトコードを離れて、import
の動作をみていこう。まずは前節のimport os.path as os_path
を振り返る。
IMPORT_NAME os.path
で os モジュールがロードされるLOAD_ATTR path
で os モジュールから os.path モジュールを取り出す- 取り出した os.path モジュールを
os_path
という名前でシンボルテーブルに登録する
前節でも書いたように、os モジュール内部では os.path モジュールを path
という名前でインポートしているので、2. の手順がうまくいくのは納得できる。しかし、それ以外のモジュール(パッケージ)ではどうだろうか?
たとえば、A.B.C というパッケージ構造があるとする。
% python
>>> import A
>>> A
<module 'A' from 'A/__init__.pyc'>
>>> dir(A)
['__builtins__', '__doc__', '__file__', '__name__', '__path__']
このように、dir()
で調べても、A モジュールに B
という属性はない。
>>> A.B
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'B'
ということは、このパッケージでは import A.B as A_B
のようなインポートは失敗しそうだ。何故なら A モジュールに B
という属性はないのだから、上の手順では 2. で失敗するはずである。
まずは念のため、バイトコードも確認しておこう。
>>> dis.disassemble(compile('import A.B as A_B', '', 'exec'))
1 0 LOAD_CONST 0 (-1)
3 LOAD_CONST 1 (None)
6 IMPORT_NAME 0 (A.B)
9 LOAD_ATTR 1 (B)
12 STORE_NAME 2 (A_B)
15 LOAD_CONST 1 (None)
18 RETURN_VALUE
前節でみたものと同じだ。やはり、LOAD_ATTR
で B
を取り出そうとしている。では、実行してみる。
>>> import A.B as A_B
>>>
おかしい。成功してしまった。
>>> A_B
<module 'A.B' from 'A/B/__init__.pyc'>
ちゃんとインポートもできているようだ。どういうことだろうか? 一旦インタプリタを終了して、もう一度、今度は import A.B
を試してみよう。
% python
>>> import A.B
>>> A.B
<module 'A.B' from 'A/B/__init__.pyc'>
なんと、A.B
でモジュールにアクセスできている。更に import A
して A モジュールを調べてみると、
>>> import A
>>> A
<module 'A' from 'A/__init__.pyc'>
>>> dir(A)
['B', '__builtins__', '__doc__', '__file__', '__name__', '__path__']
A モジュールに B
が増えていることが分かる。
これで os 以外のモジュールでも import
がうまく動作する理由が分かった。おそらくは IMPORT_NAME
が実行されるときに、内部的にこのような下準備が行われていたわけだ。