Pythonで異なるディレクトリ(フォルダ)の間でスクリプトやパッケージをインポートしようとした際、多くの開発者が最初に遭遇する ModuleNotFoundError。
LLM(大規模言語モデル)に限らず、あらゆるPythonプログラミングで必須となる**「Pythonがインポート対象を探し出す仕組み」と、書籍などに登場する「検索パスの動的な追加」**の仕組みについて、根本から詳しく解説します。
Pythonのコード内で import A または from A import B と書いたとき、Pythonはファイルシステム全体を適当に探しているわけではありません。
Pythonは、**sys.path という「フォルダパスが登録された優先順位リスト」**を上から順番にチェックして、最初に見つかったフォルダからモジュール(ファイル)をロードします。
sys.path の中身をプリントすると、文字列でフォルダのパスが並んでいます。デフォルトでは、主に以下の場所が上から順に登録されています。
- 実行したスクリプトが置かれているフォルダ(カレントディレクトリ)
PYTHONPATHというOSの環境変数に登録されているフォルダ(設定されている場合)- Pythonの標準ライブラリ(
mathやosなど)がインストールされているフォルダ - 外部から pip インストールしたライブラリ(
torchやnumpyなど)が保管されているsite-packagesフォルダ
以下のようなプロジェクト構造のとき、chap04/main.py から chap03/main.py を呼び出そうとします。
llm-sandbox/
├── chap03/
│ └── main.py (インポートしたいモジュール)
└── chap04/
└── main.py (実行するメインスクリプト)
もし、そのまま chap04/main.py を実行した場合:
- Pythonは
sys.pathの一番最初(インデックス0)に、実行スクリプトがあるフォルダ(llm-sandbox/chap04/)を登録します。 from chap03.main import ...という命令を実行した時、Pythonはsys.pathの中に登録されたフォルダからchap03という名前のフォルダを探します。- しかし、
llm-sandbox/chap04/の中には、chap03フォルダは存在しません。(一つ上の親フォルダの隣にあるため)。 - 結果、「そんなフォルダは見つかりません」となり、
ModuleNotFoundErrorが発生します。
書籍やサンプルコードでよく見かける以下の数行は、**「プログラムの実行時に、Pythonの探し物リスト (sys.path) にプロジェクトのルートフォルダのパスを最優先で割り込ませる」**という処理を行っています。
from pathlib import Path
import sys
# 1. 自分の親フォルダ、さらにその親フォルダ (ルート) の絶対パスを解決する
ROOT = Path(__file__).resolve().parents[1]
# 2. 検索パスリスト (sys.path) の最優先位置 (インデックス 0) に登録する
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))__file__: 現在実行されているスクリプトファイル自身(main.py)のパスを指す、Pythonの組み込み特殊変数です。resolve(): シンボリックリンクや相対パス表記(../など)を解決し、OSや実行環境に依存しない**完全な「絶対パス」**に変換します。parents[N]:pathlib.Pathの便利な機能で、パスを上の階層に遡ります。parents[0]: 1つ上の親フォルダ(例:chap04/)parents[1]: 2つ上の親フォルダ(例:llm-sandbox/── これがプロジェクトのルートになります)
sys.path.insert(0, str(ROOT)): 検索先リストsys.pathは単なる Python のリスト(list)です。append()を使うとリストの末尾(最悪優先)に追加されてしまいますが、insert(0, ...)を使うことで、**リストの先頭(最優先)**に割り込ませることができます。これにより、Pythonに真っ先にプロジェクトのルートフォルダを探しに行かせることができます。
コードに sys.path.insert をいちいち書くのが面倒な場合、コマンドラインからPythonを実行するオプションを変えるだけで解決することもできます。
プロジェクトのルートフォルダ(llm-sandbox/)に移動した状態で、以下のように実行します。
# ルートに移動
cd C:\Users\owner\Documents\lab\llm-sandbox
# -m オプションを使い、ドット区切りの「モジュール」として実行する
python -m chap04.main-m(Moduleの略)を付けて実行すると、Pythonは**「あなたが現在コマンドを実行しているカレントディレクトリ(llm-sandbox/)」を自動的に sys.path の先頭(インデックス0番目)に設定**してくれます。
これにより、コード内に sys.path.insert を1行も書かなくても、自動的に from chap03.main import ... の解決が成功するようになります。
- インポート挙動実証デモ: import_demo.py (実際にインポートエラーが発生する様子、sys.path.insert によって解決する様子、および sys.path の優先順位リストが変化する様子を数値とコンソール上で完全に体感できるスクリプトです)