Edited at

pythonのパッケージをimportしてもモジュールはimportされない話

More than 1 year has passed since last update.

タイトルで終わるだけの話なんですけどね。

python v3.6.5で確認しています。


問題

pythonのパッケージの作り方はドキュメントに書いてあるとおりで、__init__.pyをディレクトリ内に置くだけです。

foo/

__init__.py
bar.py

これでfooパッケージにbarモジュールが収まります。

ところで、標準モジュールにはosモジュールとos.pathモジュールがあります。直観通り、以下のように使えます。

import os

os # ok
os.path # ok

では先程のfooパッケージでも同じことができるのか?

import foo

foo # ok
foo.bar # AttributeError: module 'foo' has no attribute 'bar'

面白いのが、以下のようにすると使えることです。

import foo

from foo import bar

foo # ok
foo.bar # ok


理解

結論はここに書いてあるのですが、パッケージのインポートは__init__.pyを実行するだけで、ディレクトリ内のモジュールは直接関係ありません。つまりosos.pathが特殊なだけです。

ではどうやってそのように特殊にするかといえば、__init__.pyで属性を定義すればよいです。


foo/__init__.py

from . import bar


では、from foo import barとすることでfoo.barが有効になるのはなぜなのか。ここfromつきimportの説明があります。


from 節で指定されたモジュールを見付け出し、必要であればロードし初期化する;

import 節で指定されたそれぞれの識別子に対し以下の処理を行う:

インポートされたモジュールがその識別子名の属性を持っているかを確認する

持っていなかった場合は、その識別子名でサブモジュールのインポートを試み、再度その属性がインポートされたモジュールにあるか確認する

属性が見付からない場合は、 ImportError を送出する。

属性が見付かった場合は、 as 節があるならそこの名前、そうでないなら属性名を使って、その値への参照がローカル名前空間に保存される


「その識別子名でサブモジュールのインポートを試み、再度その属性がインポートされたモジュールにあるか確認する。属性が見付からない場合は、 ImportError を送出する」これにより、ImportErrorが送出されなければ、インポートされたモジュール(foo)

にその属性(bar)があるということになります1。つまり、foo.barでアクセスできるということです。

注意点として、fromで指定されたモジュールはロードはされますが、その名前自体は束縛されません。

from foo import bar

foo.bar # NameError: name 'foo' is not defined





  1. 逆にImportErrorが起こるケースがわからないのですが