TL;DR
- Pythonでimportする際は、「実行ファイル(厳密には
モジュール検索パス
)から見たパス」で考える-
例1の
b_2.py
参照
-
例1の
この記事を書くに至った経緯
- 自分も数年Pythonを扱って来ましたが、
ModuleNotFoundError: No module named 'xxx'
エラーを親の顔ほど見ましたし、苦しめられてきました - 今までimportを相対的に考えていましたが、絶対的に考えることにより上手くいった部分があったので、共有しようと思います
- 実際にコードで説明した方がわかりやすいと思うので、例を交えながら説明します
注意点
- 実行環境は
Python 3.10.0
です - 例で記述したコードは最短にすることを最重視しています
-
__init__.py
については本記事では対象外とさせていただきます- これについても情報が散見される印象を受けるので、いつかまとめたいとは思っていますが……
-
Pythonの公式ドキュメントにも
絶対 import
という表現がありますが、本記事では少し違う意味合いで使用している可能性があります - 「詰まることがなくなった」と言っているだけで、設計的に正しいかは別の話です……
本題
例1. 二階層の場合
- ディレクトリ構成は以下で、実行するファイルは
a_1.py
とします-
a_1.py
→b_2.py
→b_3.py
とimportしていきます
-
ディレクトリ構成
.
├── a_1.py
└── b_dir
├── b_2.py
└── b_3.py
- 各ファイルの内容は以下です
a_1.py
from b_dir import b_2
print(b_2.main())
- この記事で一番伝えたいのは、次の
b_2.py
のコードです
b_2.py
# import b_3 # これだと動かない。
# 「ModuleNotFoundError: No module named 'b_3'」エラーが出る。
# 現在のファイルから見て、相対的にimportしようという考え方。
from b_dir import b_3 # 動く。
# 実行するファイルを基準として、絶対的にimportしようとする考え方。
def main():
return b_3.main()
b_3.py
def main():
return "hoge"
- 階層が下のファイルからも、カレントのファイルからではなく実行するファイルを基準としてimportしたら、上手くいきました
実行結果
% python3 a_1.py
hoge
- なお、上の階層から実行しても問題なく実行できます
% python3 a_dir/a_1.py
hoge
もう少し深掘り
例2. 階層がもう1つ深い場合
- 同様の考え方でやればよいです
ディレクトリ構成
.
├── a_1.py
└── b_dir
├── b_2.py
└── c_dir
├── c_3.py
└── c_4.py
- 実行するファイルは
a_1.py
とします-
a_1.py
→b_2.py
→c_3.py
→c_4.py
とimportしていきます
-
a_1.py
from b_dir import b_2
print(b_2.main())
b_2.py
# from c_dir import c_3 #これだと動かない
from b_dir.c_dir import c_3
def main():
return c_3.main()
c_3.py
# import c_4 # これだと動かない
from b_dir.c_dir import c_4
def main():
return c_4.main()
c_4.py
def main():
return "hoge"
補足: 上の階層のファイルをimportしたい場合
-
a.py
→c_3.py
→b_2.py
の順にimportしたい場合でも、c_3.py
ではこのようにimportすれば良いです
c_3.py
from b_dir import b_2
# from ..b_dir とする必要はない
例3. 実行ファイルより上の階層をimportしたい場合
ディレクトリ構成
.
├── a_dir
│ ├── a_1.py
│ └── b_dir
│ └── b_2.py
└── ex_a_dir
├── ex_a_1.py
└── ex_b_dir
└── ex_b_2.py
-
a_1.py
を実行ファイルとして、そこからex_a_1.py
をimportしたい場合ですね - 「詰まることがなくなった」と大口を叩いておいて申し訳ないですが、この場合はまた話が難しくなります
-
sys.path.append
を使えば一応できます- が、その追加先のファイルで、別のファイルをimportしたい場合にまたややこしくなります
- 上の例だと
ex_a_1.py
からex_b_2.py
をimportしたい場合
- 上の例だと
- これで対応していいのは、import先が「同一モジュール」と捉えられる場合に限ると考えています
- が、その追加先のファイルで、別のファイルをimportしたい場合にまたややこしくなります
- そもそも実行ファイルはルートにあることが多いと思うので、実行ファイルより上の階層をimportしたい場合は、対象は独立しているべきであり、
パッケージ化
するのが正しいと考えています- 上の例だと
ex_a_dir
をパッケージ化する
- 上の例だと
- この辺りの話はPython公式ドキュメントの 6. モジュール をご参照ください。(丸投げ)
まとめ
- Pythonのimport周りは本当に詰まりやすいと思いますが、本記事で解消できる部分があれば幸いです
- 本記事の内容は、Python公式ドキュメントの 6.1.2. モジュール検索パス の内容に深く関わっていますので、こちらもご参照ください
- 本記事は「こうしたら動いた」という紹介ですが、「こうすると設計的に正しい」ということが言えるよう、引き続きimport周りの学習を続けていこうと思います