Pythonで開発していると、PyCharmだと動く相対パス/相対インポートが、VS Codeやターミナルだと動かないという現象にかなり高い確率で出会います。 pythontutorials
この記事では、「書き方」の違いではなく、IDEの設定の違いが原因であることを整理しつつ、どの環境でも安定して動く書き方のパターンをまとめます。 pythontutorials
前提:Pythonの相対パス・相対インポートはIDEに依存しない
まず押さえておきたいポイントは、
-
open("data/input.csv")のようなファイルパスの相対指定は
→ 「カレントディレクトリ(Working Directory)からの相対」で解決される。 stackoverflow -
from . import x,from ..pkg import yのようなパッケージ相対インポートの文法も
→ Pythonの仕様で決まっており、IDEによって変わるものではない。 stackoverflow
にもかかわらず挙動が変わるのは、IDEが「どこをカレントディレクトリにしてスクリプトを起動しているか」「どこをimportの基準にしているか」が違うからです。 stackoverflow
PyCharm側の特徴:Working Directoryをかなり面倒見てくれる
PyCharmで右クリック → Runしたとき、デフォルトのWorking Directoryは以下のようになります。 stackoverflow
- スクリプトがプロジェクト直下にある場合
→ プロジェクトルートがWorking Directoryになる($PROJECT_DIR$)。 stackoverflow - サブフォルダにある場合
→ そのサブフォルダ、またはモジュールディレクトリ($MODULE_DIR$)がWorking Directoryになる。 pythontutorials
そのため、例えば以下のような構成で:
my_project/
├── data/
│ └── input.csv
└── src/
└── main.py
main.py をPyCharmから実行して open("data/input.csv") と書いても、PyCharmが上手くWorking Directoryをプロジェクトルートにしてくれる設定になっていると普通に動きます。 stackoverflow
逆に、PyCharmで相対パスがコケる場合は、Run Configurationで設定されているWorking Directoryがズレていることがほとんどです。 stackoverflow
- 対処:Run Configurationの「Working directory」を
- プロジェクトルート(
$PROJECT_DIR$)に揃える - もしくはスクリプトと同じフォルダ(
$MODULE_DIR$)に揃える
- プロジェクトルート(
VS Code側の特徴:ターミナルとほぼ同じ、あまり手は出してくれない
VS Code + Python拡張の場合、基本的な挙動はターミナルで python your_script.py を叩いたときに近いです。 discuss.python
- Working Directory
- デバッガ実行時は
launch.jsonの"cwd"設定に依存する。 stackoverflow - 何も設定しなければ、通常はワークスペースルートやターミナルのカレントディレクトリが使われる。 apxml
- デバッガ実行時は
- インポートパス
- PyCharmのように「プロジェクトルートを自動でimportパスに含める」という優しさが弱く、
PYTHONPATHやsettings.jsonの"python.analysis.extraPaths"などを自分で調整することが多い。 stefanocivelli
- PyCharmのように「プロジェクトルートを自動でimportパスに含める」という優しさが弱く、
その結果、PyCharmでは通っていた次のようなコードがVS Codeでコケがちです。
from my_project.utils import helper # PyCharmではOKだった
VS Code側ではプロジェクトルートがimportパスに入っていないため、ModuleNotFoundError になる、というパターンです。 stefanocivelli
- 対処の一例(VS CodeをPyCharmっぽくする)
-
.vscode/settings.jsonに以下のように書いて、プロジェクトルートをimportパスに追加する。 stackoverflow{ "python.analysis.extraPaths": [ "./" // ワークスペースルート ] } -
あるいは、
PYTHONPATHを設定してプロジェクトルートを追加する。 youtube
-
IDEに依存しない相対パスの書き方パターン
IDEごとの挙動に振り回されたくない場合、「カレントディレクトリ」ではなく「スクリプト自身の場所」を基準にパスを組み立てるのが定番パターンです。 qiita
基本パターン:__file__ + pathlib
from pathlib import Path
# このファイル(モジュール)自身があるディレクトリ
BASE_DIR = Path(__file__).resolve().parent
# data/input.csv へのパス
DATA_PATH = BASE_DIR / "data" / "input.csv"
with DATA_PATH.open("r", encoding="utf-8") as f:
content = f.read()
ポイント:
-
__file__で「このモジュールのパス」を取得する。 chayarokurokuro.hatenablog -
Path(__file__).resolve().parentで「モジュールが存在するディレクトリ」を絶対パスで取得する。 pythontutorials - あとは
BASE_DIR / "data" / "input.csv"のように相対パスを組み立てる。 qiita
こうしておけば、
- PyCharmから実行しても
- VS Codeのデバッガから実行しても
- ターミナルで
python -m package.moduleと実行しても
常に「このファイルの場所」からの相対パスになるため、Working Directoryの違いに依存しません。 pythontutorials
階層を跨ぐ場合:parent を使って上に上がる
例えば以下の構成で、src/my_module/data_processor.py から data/global_input.txt を読みたい場合: pythontutorials
my_project/
├── data/
│ └── global_input.txt
└── src/
└── my_module/
└── data_processor.py
from pathlib import Path
MODULE_DIR = Path(__file__).resolve().parent # .../src/my_module
PROJECT_ROOT = MODULE_DIR.parent.parent # .../my_project
DATA_PATH = PROJECT_ROOT / "data" / "global_input.txt"
parent を何回辿るかで、好きな階層を基準にできます。 pythontutorials
まとめ:何が違って、どう書くのが安全か
整理すると:
- Pythonの文法としての「相対パス」「相対インポート」はPyCharmでもVS Codeでも同じ。 stackoverflow
- 違って見えるのは
- PyCharmがRun Configurationで設定するWorking Directoryとimport pathの扱いがかなり親切で、 pythontutorials
- VS Codeはターミナル実行に近い素直な挙動で、設定しない限りプロジェクトルートを特別扱いしないため。 stefanocivelli
- IDEに依存しないための実践的なパターンは
- ファイルアクセス:
__file__+pathlib.Pathで「モジュールの場所基準」でパスを組む。 chayarokurokuro.hatenablog - import:プロジェクト構造をパッケージとして成立させた上で、必要なら
PYTHONPATHや VS Code設定でルートを明示的に通す。 discuss.python
- ファイルアクセス:
おまけ:ハマりやすい「よくある落とし穴」
- 「PyCharmで動いたからOKだと思っていたら、サーバにデプロイしたら全部FileNotFoundError/ModuleNotFoundError」
→ PyCharmのWorking Directoryマジックに依存していたパターン。 reddit - Jupyter Notebookで
os.getcwd()を基準にパスを書いていて、他のスクリプトから呼ぶと壊れる
→ Notebook基準のCWDと、スクリプト実行時のCWDが違う問題。 chayarokurokuro.hatenablog
このあたりを一度整理しておくと、「IDE変えたら動かなくなった…」というストレスをかなり減らせます。