問題提起
- Pythonは、上位のディレクトリや遠く離れたディレクトリからimportできない
- Pythonの相対importは直感的ではない
- 「そのファイル」からの相対位置指定ではない。呼び出し元ファイル(最初に実行されるpythonファイル)が変わると、相対importの参照がずれる
- Pythonでは、自作ツールを複数のプロジェクトからimportするときのディレクトリ構成が難しい
- 上位のディレクトリ・兄弟のディレクトリのimportが難しいため
結論
相対パス参照のパッケージをインストール
pip install relpath
from relpath import add_import_path
add_import_path("../") # ここで、importしたいツールの場所を相対参照で指定
from my_module import some_function
詳細説明 (相対import)
relpath
パッケージを利用すると、下記の例のように、
モジュールの直感的な相対importを実現できます。
from relpath import add_import_path
add_import_path("../")
from my_module import some_function
some_function()
上記の例を見ると、単にsys.path.append("../")
としても動作するように思われます。
しかし、プロジェクトフォルダの階層構造が複雑で、1つのモジュールが別々の場所から使われるような場合には、sys.path.append("../")
では対応できないことがあります。
そのため、相対importを実現したいときは、常にrelpath
パッケージのadd_import_path
を利用することを推奨します。
その他の使い方
relpath
パッケージを使うと、importに限らず、
直感的な相対パス参照が可能です。
例えば、下記のような複数のpythonファイルからなるプロジェクトを考えます。
.
`-- project_folder
|-- parts
| |-- data.txt
| `-- script_B.py
`-- script_A.py
script_A.py
の中では下記のように、script_B.py
を利用します。
# script_A.py
# load script_B.py
from parts.script_B import get_data
print(get_data())
この場合に、下記のコード例のように、
script_B.py
から"./data.txt"
を相対的に読み込もうとすると失敗します。(注1)
(注1)
厳密には、script_A.py
からの相対パス指定をすれば読み込めますが、
呼び出し元が別の場所に変更された場合、正常に動作しなくなるので、メンテナンス性が悪くなります。
これを回避するため、relpath
パッケージの利用を推奨します。
# script_B.py
def get_data():
with open("./data.txt", "r") as f: # -> FileNotFoundError: [Errno 2] No such file or directory: './data.txt'
return f.read()
そこで、relpath
パッケージを使って下記のように書くと、
"./data.txt"
を相対的に読み込めるようになります。(注2)
# script_B.py
from relpath import rel2abs
def get_data():
with open(rel2abs("./data.txt"), "r") as f: # -> NO ERROR!!
return f.read()
(注2)
相対パスに関するpythonの仕様は、必ずしも間違いというわけではありません。
pythonの仕様(相対パスの指定が、記述するファイルの場所に関わらず、常に最初の呼び出し元を基準として解釈される仕様)には、
プログラムを開発する中でもしファイル読み込み等の命令を記述する場所(ファイル)が変更になった場合でも、
パス指定方法の変更が不要になるという利点があります。
relpath
パッケージは、pythonの仕様の他に、プログラマーにもう一つの選択肢を与える手段に過ぎないので、
状況に応じて利用の要否を検討することを推奨します。
参考
-
relpath
パッケージのPyPIリンク