Pythonプロジェクトにおけるパス処理とモジュール参照
1. プロジェクトフォルダ構成例(MVVMを意識した例)
MVVMパターンを意識してプロジェクトフォルダを作成すると、以下のようになります。
ProjectRoot/
├─ Sources/
│ ├─ Common/
│ │ ├─ Common_A.py
│ │ └─ Common_B.py
│ ├─ Models/
│ │ ├─ Model_A.py
│ │ └─ Model_B.py
│ ├─ ViewModels/
│ │ └─ ViewModel_A.py
│ └─ Views/
│ ├─ View_A.py
│ └─ View_B.py
└─ main.py
Sources/ 以下はプロジェクトのメインソース。
Common や Models は、実行環境に依存しない共通モジュールをまとめたフォルダ。
ViewModels はアプリのロジック層、Views はUI層。
main.py がアプリのエントリポイント。
2. モジュールが見つからない(ModuleNotFoundError)の問題
ProjectRootで main.py を実行すればモジュールは参照可能です。
from Sources.Common import Common_B # OK
しかし、サブモジュールや単体ファイルで動作確認をしたい場合は問題が発生します。
from Sources.Common import Common_B
実行結果例:
Traceback (most recent call last):
File "Common_A.py", line 1, in <module>
ModuleNotFoundError: No module named 'Sources.Common'
原因
Pythonはデフォルトで実行ファイルのディレクトリや標準ライブラリしか参照しません。
サブモジュールから実行すると、プロジェクト内の Sources フォルダを見つけられず、ModuleNotFoundError が発生します。
発生環境
- サブ環境のPython実行(python Sources/Common/Common_A.py など)
- PyInstallerでビルドした場合
- Buildozer / Android 実行環境
3. 簡単な解決法 sys.pathによる相対パス追加
Pyinstaller,Buildozerに対応しなくていいなら、下記の書き方で良いです。
この書き方なら、フォルダ部分は全部sys.pathに任せられるので、モジュール名だけで参照してくれます。
import sys
# 必要なフォルダを追加
sys.path.append("./Sources/Common")
sys.path.append("./Sources/Models")
sys.path.append("./Sources/ViewModels")
sys.path.append("./Sources/Views")
sys.path.append("./Common")
sys.path.append("./Models")
sys.path.append("./ViewModels")
sys.path.append("./Views")
import Common_A
import Common_B
4 簡単な解決法2 sys.pathによる絶対パスの追加
PyInstallerでコンパイルが通る程度の対応としては、以下の3行と、ルートからのモジュール指定する対応で十分です。ただし、PyInstallerやBuildozerの実行環境ではPathの問題が出る可能性があるとのこと。
import sys, os
source_directory = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.abspath(os.path.join(source_directory, "..", ".."))
sys.path.insert(0, project_root)
from Sources.Common import Common_A
from Sources.Models import Model_B
from Sources.ViewModels import ViewModel_A
from Sources.Views import View_B
これをファイル冒頭に入れるだけで、ProjectRoot 直下を探索対象に追加できます。
これにより、すべてのモジュールを[from ルート基準のPath import モジュール名]の記述でimportできます。
4. PyInstaller対応解決方法 実行環境別ベースパス設定
少し面倒なので、できればもっと簡単な解決法を知りたいところですが、一応私の環境では下記の方法でやっています。
Buildozerについてはこれから調査なので何とも言えませんが・・・
(※ Buildozer用の回路もChatGPTの情報にすぎません。Buildozerの環境が変わったのかビルドできず検証ができない・・・。2025年版作らないとかも。)
from pathlib import Path
import sys
from pathlib import Path
# --- 実行環境によるベースパスの切り替え ---
if getattr(sys, 'frozen', False): # PyInstaller 実行時
base_directory = Path(sys._MEIPASS) # ← str を Path に変換!
elif getattr(sys, 'android_bootstrap', False): # Buildozer / Android
from android.storage import app_storage_path
base_directory = Path(app_storage_path())
else: # 通常Python実行
base_directory = Path(__file__).resolve().parent
# --- プロジェクトルートをパスに追加 ---
source_directory = base_directory.parent
project_root = source_directory.parent
if str(project_root) not in sys.path:
sys.path.insert(0, str(project_root))
from Sources.Views import View_B
from Sources.Common import Common_A
from Sources.Common import Common_B
from Sources.ViewModels import ViewModel_A
from Sources.Models import Model_A