Python の import について調べてみた。第一感よりかなり複雑だ。みかけの整然さの裏にひそむドロドロは Python ではよくあること。
import はモジュールを生成する
import 文はモジュールを生成する命令である。モジュールは辞書で実装されたオブジェクトで、属性参照 m.x が m.__dict__["x"] として評価されるオブジェクトである。m.x や m.y に関数やクラスを代入することで名前空間を作成できる(x や y という名前が呼び側の名前空間を侵犯せず m. という名前の元にまとめられているという意味)。m.x や m.y に代入される関数やクラスは、Python ソースファイル(.py)に記述されている。import は .py ファイル(モジュールファイルと呼ぼう)をモジュールオブジェクトに変換する命令といえる。
import m は、空のモジュール M を生成し、ファイル m.py を実行する。その際トップレベルの代入はモジュール M の属性参照への代入として実行される(たとえば def f(): により M.f に関数オブジェクトが代入される)。最後に import を呼び出したスコープにおけるローカルな名前空間で m にモジュール M を代入する。
m.py に f() が定義されていた場合、import m を記述したスコープからは m.f() で呼び出すことができる。
モジュールファイル m.py はどこに置かれたファイルなのだろうか?import で指定されたファイルは sys.path に登録されたディレクトリから探されるので、sys.path にあるディレクトリのどこかということになる。
モジュールの階層化
上で説明した単純な import だと、モジュールファイルの名前空間が枯渇する恐れがある。また、複数のファイルからなる大規模なライブラリも作りにくい。そこでファイルを階層的に配置できる方法が提供されている。これをパッケージと呼ぶ。
パッケージは __init__.py ファイルを含むディレクトリである。
import p.m は、空のモジュール P を生成し、ファイル p/__init__.py を実行する。その際トップレベルの代入はモジュール P の属性参照への代入として実行される(たとえば def f(): により P.f に関数オブジェクトが代入される)。空のモジュール M を生成し、ファイル p/m.py を実行する。その際トップレベルの代入はモジュール M の属性参照への代入として実行される(たとえば def f(): により M.f に関数オブジェクトが代入される)。P.m に M を代入し、最後に import を呼び出したスコープにおけるローカルな名前空間で p にモジュール P を代入する。
p/m.py に f() が定義されていた場合、import p.m を記述したスコープからは p.m.f() として呼び出すことができる。
モジュールファイルとしてのパッケージ
import で指定するモジュール名に対応するのがモジュールファイルである必要はなく、パッケージでもよい。その場合、最終的に実行されるファイルは __init__.py のみとなる。import p は p/__init__.py をモジュールに変換する(そしてその他のファイルは実行しない)。
ネストしたパッケージ
パッケージの中にパッケージを入れてネストさせる場合、末端のモジュールにいたる全てのディレクトリに __init__.py が必要である。
as によるモジュールの別名
import p.m as m と指定すると、import を呼び出したスコープのローカルな名前空間で p に P を代入するのではなく m に M (つまり P.m) が代入される。長い階層化名を持つモジュールを短い名前でアクセスするために用いる。
p/m.py に f() が定義されていた場合、import p.m as m を記述したスコープからは m.f() として呼び出すことができる。
from による定義のコピー
from p.m import f とすると、import を呼び出したスコープのローカルな名前空間で p に P を代入するのではなく f に M.f (つまり P.m.f)が代入される。
p/m.py に f() が定義されていた場合、from p.m import f を記述したスコープからは f() として呼び出すことができる。
from によるモジュールの生成
from p import m は、p に対応するパッケージディレクトリからモジュールファイル m.py を読むのにも使える。from p imprt m は、空のモジュール P を生成し、ファイル p/__init__.py を実行する。そして空のモジュール M を生成し、ファイル p/m.py を実行する。最後に import を呼び出したスコープにおけるローカルな名前空間で m にモジュール M を代入する。
p/m.py に f() が定義されていた場合、from p import m を記述したスコープからは m.f() として呼び出すことができる。
p/__init__.py に m の定義があった場合、p/m.py は実行されず、import を呼び出したスコープで m は P.m を指すことになる。
この形式の from import で階層が記述できるのは from 部分だけである。つまり from p.q import m とは書けるが、from p import q.m とは書けない。
この形式の from import の仕様はどこに記述してあるのだろうか?見当たらない。
この形式の from import を用いると1つのパッケージから複数のモジュールを読み込むことを簡単に記述できる。from p.q.r import m1, m2, m3 のように。しかしどうも存在意義があまり感じられない。
import * による全ての指定
from p.m import * により、モジュールファイル m.py に存在する「全ての」定義をローカルな名前空間での同名の変数に代入できる。
「全て」というのは以下を意味する。m.py に __all__ という変数に文字列のリストが代入されていた場合、その文字列の名前の定義だけが「全て」となる。そうでない場合、_ で始まらない定義が「全て」となる。
なお、__all__ にしても _ で始まる名前にしても import * のときにだけ効果があるものであって、個別に具体的な名前で import したり属性参照するのを防ぐことはできない。そういう意味で Python のモジュールには名前を隠蔽する手段は存在しない。
import * によるモジュールの生成
from p import * で、p がパッケージの場合、__all__ に指定された文字列の名前に対応するモジュールファイルが全てモジュールに変換される。そして import の呼び出し側で、その文字列の名前を持つ変数に代入される。
from p import * で、p/__init__.py に all = [ "a", "b" ] とあった場合、p/a.py と p/b.py がモジュールに変換され、それぞれ a, b という名前で参照できるようになる。
正確を期すために
import によるモジュールファイルの実行は1回しか行われない。2度目以降の同じモジュールの import ではすでに生成されたモジュールオブジェクトが用いられる。
上で説明したようなことは Python の仕様には書いてない。仕様では、m.p のような記述が与えられたときにそれをロードしてモジュールオブジェクトを作る関数をどのように探すかなどについて記述してある。import メカニズムに割り込んでカスタマイズ可能なようになっているのだ。上の説明は C Python 組み込みのデフォルトのローダの挙動について説明している。デフォルトのローダの仕様がどこに記述してあるかは不明だ。
import は .py ファイルだけでなく他のファイルを実行することもできる(もちろん上述の loader をカスタマイズすれば何でも実行できるわけだが)。
0 件のコメント:
コメントを投稿