8
3

Pythonの「絶対import」について

Last updated at Posted at 2022-12-03

TL;DR

  • Pythonでimportする際は、「実行ファイル(厳密にはモジュール検索パス)から見たパス」で考える
    • 例1b_2.py参照

この記事を書くに至った経緯

  • 自分も数年Pythonを扱って来ましたが、ModuleNotFoundError: No module named 'xxx'エラーを親の顔ほど見ましたし、苦しめられてきました
  • 今までimportを相対的に考えていましたが、絶対的に考えることにより上手くいった部分があったので、共有しようと思います
  • 実際にコードで説明した方がわかりやすいと思うので、例を交えながら説明します

注意点

  • 実行環境はPython 3.10.0です
  • 例で記述したコードは最短にすることを最重視しています
  • __init__.pyについては本記事では対象外とさせていただきます
    • これについても情報が散見される印象を受けるので、いつかまとめたいとは思っていますが……
  • Pythonの公式ドキュメントにも絶対 importという表現がありますが、本記事では少し違う意味合いで使用している可能性があります
  • 「詰まることがなくなった」と言っているだけで、設計的に正しいかは別の話です……

本題

例1. 二階層の場合

  • ディレクトリ構成は以下で、実行するファイルはa_1.pyとします
    • a_1.pyb_2.pyb_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.pyb_2.pyc_3.pyc_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.pyc_3.pyb_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したい場合は、対象は独立しているべきであり、パッケージ化するのが正しいと考えています
    • 上の例だとex_a_dirをパッケージ化する
  • この辺りの話はPython公式ドキュメントの 6. モジュール をご参照ください。(丸投げ)

まとめ

  • Pythonのimport周りは本当に詰まりやすいと思いますが、本記事で解消できる部分があれば幸いです
  • 本記事の内容は、Python公式ドキュメントの 6.1.2. モジュール検索パス の内容に深く関わっていますので、こちらもご参照ください
  • 本記事は「こうしたら動いた」という紹介ですが、「こうすると設計的に正しい」ということが言えるよう、引き続きimport周りの学習を続けていこうと思います
8
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
3