背景
- Python で自作ライブラリ(モジュール)を作っている, Python アプリのコードのモジュール化をしている.
- 自作ライブラリのモジュールは他の自作モジュールを参照することもある
- unittester とかではしらせずらい(GUI 使っていたりして, UI のコンポーネントをテストしたい), テスト .py つくる, なんかの test フレームワーク使うのもめんどいので, モジュールに単体テストコード書いてぱぱっと時短ですませたい..
- テストファイルわけると, vscode とか使っている場合, working directory 設定とかめんどい...
-
from . import myui
とかあるけど単体で動かす場合はエラーになってしまう...
- 自作ライブラリをパッケージングしているときに, pip とかでインストールされた site-package のほうではなくて, 手元(e.g. git repo)の開発版のほうをみるようにしたい
まとめ
- 基本相対パスは使わない.
from mymod import mod1
とかimport mymod.mod1
とかの記述にする. - 親階層の参照が必要なら
python
で呼ばれる.py
でsys.path.append("..")
などでパスを通す - 開発 repo 側のソースコードの import を優先させたい場合は
PYTHONPATH
環境変数指定が推奨
前提条件
main.py
myui/
uimod.py
uimod2.py
のような構成を考えます.
python 3.3 or later で, 階層とかはそんなに複雑ではないので, __init__.py
は作らないとします.
main.py
は普段通りです.
# main.py
import myui.uimod
# もしくは from myui import mymod
myui.uimod.myfunc()
情報
ありがとうございます.
問題. 相対インポート
モジュール側で相対インポートを使っていると,
# myui/uimod.py
from . import mymod2 # 同じモジュール(同じ階層)にある mymod2.py を使う
if __name__ == '__main__':
testcode...
別ファイルからこの .py は import はできますが,
python uimod.py
で直接起動する場合は相対 import はつかえないのでエラーになります
解決その 1
- 相対は使わない
- モジュール側で別モジュールを呼ぶときは
import myui.uimod2
orfrom myui import ...
のように記述する
- モジュール側で別モジュールを呼ぶときは
- メインファイル側(モジュールを呼び出す側)で必要に応じて
sys.path.append
でパスを通しておく
tests
フォルダに, test code 作りたい場合, e.g.
main.py
tests/
test_uimod.py
test_uimod2.py
myui/
uimod.py
uimod2.py
の場合, テストコード .py
では sys.path.append("..")
でモジュールの基点(main.py
のあるフォルダ)を追加して対応.
# tests/test_uimod.py
# 起動は `tests` に入り `python test_uimod.py`
import sys
sys.path.append("..")
from myui import uimod
testcode...
https://note.nkmk.me/python-relative-import/ にありますように, sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
で, 絶対パス(__file__
基準)にするとより確実でしょう
モジュール内で親階層側にある別モジュールを呼んでいる場合も同様で, メイン側(モジュールを呼び出す側)で各モジュールへのパスを通しておく.
myui/
uimod.py
uimod2.py
components/
mycomp.py
とあって, myui.uimod.py
から components/mycomp.py
を呼んでいる場合は, モジュール側では特になにもしない.
# myui/uimod.py
# import sys
# sys.path.append("..") # components/ の親ディレクトリ
from components import mycomp
...
解決その 2
相対パスのよい点は, .py
がローカルの手元で開発しているバージョンのを呼んでいるのが確実になることです
たとえば pip パッケージ化している場合, ローカルの開発版と pip で入っているものものが混在してかちあってしまうのを防げる可能性があります.
ちょっとコードはややこしくなりますが,
モジュール .py に一緒にテストコード記述したいばあい,
if __name__ == '__main__':
sys.path.append("..")
import myui.mymod2
else:
from . import mymod2
if __name__ == '__main__':
testcode...
か, __file__
基準でより相対パス問題の少ない
if __name__ == '__main__':
sys.path.append(os.path.join(os.path.dirname(__file__), '..')
from myui import mymod2
みたいにするのがよさそうでしょうか...
モジュール内で親階層側にある別モジュールを参照している場合は, この場合モジュール側に適宜 sys.path.append("..")
あたりでパスを追加しておく必要があります.
# myui/uimod.py
if __name__ == '__main__':
import sys
sys.path.append("..") # or `components/` の親ディレクトリ
from components import mycomp
if __name__ == '__main__':
sys.path.append("..")
import myui.mymod2
else:
from . import mymod2
if __name__ == '__main__':
testcode...
ローカルのモジュールの探索を優先させる
もしくは, pip
などで site-packages
にインストールされているのと手元ローカル版がある場合に, 手元ローカル版を優先させる場合は PYTHONPATH
を設定で対応がよいでしょう.
(メインで実行する .py
がローカルモジュールの親ディレクトリに配置されていない場合)
ただ, .py
で PYTHONPATH
環境変数を実行時指定
os.environ["PYTHONPATH"] = os.path.join(os.path.dirname(__file__), "..")`
のようにしてもうまく探索されない(環境変数指定の場合は, python
起動時でないと反映されないぽい)です.
PYTHONPATH
環境変数設定して起動がめんどうで, .py
で完結させたい場合, sys.path
で探索がよいでしょう.
ただ, sys.path
でのパス解決は昇順(リストの最初から)なので, append()
で最後につけると, pip とかの site-packages パスが優先になってしまいます.
Python list には prepend
がありませんので, insert(0, ...)
あたりでリストの最初に追加で対応します!
sys.path.insert(0, os.path.join(os.path.basedir(__file__), ".."))