Python で自作ライブラリの import を極めたいメモ

## 背景

* 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` は普段通りです.

```python
# main.py

import myui.uimod
# もしくは from myui import mymod

myui.uimod.myfunc()

```

 
## 情報

https://note.nkmk.me/python-relative-import/

ありがとうございます.

## 問題. 相対インポート

モジュール側で相対インポートを使っていると, 


```
# myui/uimod.py
from . import mymod2 # 同じモジュール(同じ階層)にある mymod2.py を使う


if __name__ == '__main__':
  testcode...

```

別ファイルからこの .py は import はできますが,
`python　uimod.py` で直接起動する場合は相対 import はつかえないのでエラーになります :cry:


## 解決その 1

* 相対は使わない
  * モジュール側で別モジュールを呼ぶときは `import myui.uimod2` or `from myui import ...` のように記述する
* メインファイル側(モジュールを呼び出す側)で必要に応じて `sys.path.append` でパスを通しておく

`tests` フォルダに, test code 作りたい場合, e.g.

```python
main.py
tests/
  test_uimod.py
  test_uimod2.py
myui/
  uimod.py
  uimod2.py
```

の場合, テストコード `.py` では `sys.path.append("..")` でモジュールの基点(`main.py` のあるフォルダ)を追加して対応.

```python
# 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 に一緒にテストコード記述したいばあい,

```python
if __name__ == '__main__':
  sys.path.append("..")
  import myui.mymod2
else:
  from . import mymod2


if __name__ == '__main__':
  testcode...
```

か, `__file__` 基準でより相対パス問題の少ない

```python
if __name__ == '__main__':
  sys.path.append(os.path.join(os.path.dirname(__file__), '..')

from myui import mymod2
```

みたいにするのがよさそうでしょうか...

モジュール内で親階層側にある別モジュールを参照している場合は, この場合モジュール側に適宜 `sys.path.append("..")` あたりでパスを追加しておく必要があります.

```python
# 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` がローカルモジュールの親ディレクトリに配置されて**いない**場合)

https://docs.python.org/3/tutorial/modules.html#the-module-search-path

ただ, `.py` で `PYTHONPATH` 環境変数を実行時指定

```python
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, ...)` あたりでリストの最初に追加で対応します!

https://stackoverflow.com/questions/31414041/how-to-prepend-a-path-to-sys-path-in-python

```python
sys.path.insert(0, os.path.join(os.path.basedir(__file__), ".."))
```