Python+VScode 環境変数PYTHONPATHで自作ライブラリにパスを通したい！

## はじめに
自作ライブラリをimportする方法として，以下の方法はよく知られています．

```Python
lib_path = os.path.join(os.path.dirname(__file__), '..', 'hoge')
sys.path.append(lib_path)
```

しかし，この方法ではすべてのスクリプトファイルに上記のプログラムを記述する必要があり，美しくありません．
本記事では別の方法として環境変数にライブラリのパスを保存させておくことで，上記のプログラムを**二度と**書かなくて済むことを目指します．

要点だけ知りたい方は[解決方法](https://qiita.com/1taroh/items/5d757d5a8aafff8c03e2#%E8%A7%A3%E6%B1%BA%E6%96%B9%E6%B3%95)以降を見てください．
## 環境
* Visual Studio Code
* Python 3.12 (Anaconda ではない生Python)
* Windows 11

## 初期状態の挙動の確認

### ディレクトリ構造

現在のディレクトリ構造を以下のものとします．
```
myproject
├───lib
│       lib1.py
│
├───notebook
│   ├───experiment1
│           notebook1.ipynb
│    
└───src
        src1.py
```
各フォルダは以下の役割があります．
* `lib`: 自作ライブラリ
* `notebook`: jupyter notebookを置く
* `experiment1`: jupyter notebook で用いる分析，対象等ごとの区切り
* `src`: jupyter notebook から py ファイルに清書したものを置く

本記事の目標は環境変数を用いることで `src1.py` や `notebook1.ipynb` から `lib1.py` を呼び出せるようにすることです．

### 各ファイルの中身

`lib1.py` ではプロット用の関数を定義しています．
```Python: lib1.py
import numpy as np
import matplotlib.pyplot as plt

def plot_amp_and_ph(z):
    r = np.abs(z)
    theta = np.angle(z)
    fig, ax = plt.subplots(2,1)
    ax[0].plot(r)
    ax[1].plot(theta)
```

`notebook1.ipynb` では `lib1` をライブラリとして呼び出し， `plot_amp_and_ph` 関数を呼び出します．

```Python: notebook1.ipynb
import numpy as np
import matplotlib.pyplot as plt
import lib1

theta = np.linspace(0, 6*np.pi)
z = np.exp(1j*theta)

lib1.plot_amp_and_ph(z)
plt.show()
```

`src1.py` は  `notebook1.ipynb`  全く同じコードです．しかし，ディレクトリ構造における位置が全く異なります．本記事で扱う方法では，ライブラリとの相対的な位置関係が異なっていても使用することができます．

```Python: src1.py
import numpy as np
import matplotlib.pyplot as plt
import lib1

theta = np.linspace(0, 6*np.pi)
z = np.exp(1j*theta)

lib1.plot_amp_and_ph(z)
plt.show()
```

### プログラムの実行
現在の状態でプログラムを実行してみます．
```bash
>python src/src1.py 
# Traceback (most recent call last):
#  File "path-to-myproject\src\src1.py", line 3, in <module>
#    import lib1
#ModuleNotFoundError: No module named 'lib1'
```
当然ながら，パスが通っていないので実行できません．

## 解決方法
### 環境変数の保存

パスを通すために，本記事の主題である環境変数に `lib` フォルダへのパスを保存します．
`PYTHONPATH` という環境変数に保存するのですが，まずはその中身を確認します．

```bash
>echo %PYTHONPATH%
# %PYTHONPATH%
```
環境変数 `PYTHONPATH` は使われていないようです．
Anaconda を使っている人は`site-pachages` へのパスが定義されているかもしれません．

環境変数を定義するために， `.env`ファイルを作成して `myproject` 直下に保存します．

```.env
PYTHONPATH=path-to-myproject\lib
```
これを保存することで jupyter notebook へのパスだけは通るようになります．実行時にライブラリが読み込まれるだけではなく，VSCode側も認識しているので，コード補完などが効くようになります．

しかし，`py` ファイルを実行するときには変わらずエラーしてしまいます．
これは，使用しているターミナルが作成した環境変数を認識していないためです．
`echo %PYTHONPATH%` を実行することで，環境変数が認識されていないことを確認できます．

### インタプリターに環境変数を認識させる
上述の問題に対応するため，以下の方法を用いることで，環境変数を Python インタプリターに認識させます．

1. VSCode上で仮想環境を立て，必要なライブラリをインストールする
2. `ctrl + shift + P` 入力により，仮想環境のPythonインタプリターを選択する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/642842/e9e4efa5-7b03-cc9c-3041-e72715bb4120.png)

以上の操作を実行すると，環境変数が認識され，`src1.py` をターミナルで実行することができるようになります．

## 結論
1. `.env`ファイルにライブラリのパスを保存
2. 仮想環境を立てる
3. `ctrl + shift + P` で仮想環境のPythonインタプリターを選択

## おわりに
インターネット上に情報があまりなかったので，この調査で私の土日は吹っ飛びました．
が，これを見ている方々のためになれば幸いです．

## 追記
上記の方法ではPythonインタープリターを選択すれば自動的に.envファイルが認識されるというある種間接的な方法でした．さらなる調査により明示的に.envファイルを認識させる方法が分かったので追記します．

### workspace を使う
workspace を使っている場合には，.envファイルを `${workspaceFolder}/.env` に設置すると実行時に自動的に読み込んでくれます．
以下のプログラムで確認できます．
```Python
import os
print(ps.environ["PYTHONPATH"])
    # >>> 'path/to/your/pythonpath'
```

## 参考文献
* https://code.visualstudio.com/docs/python/environments#_environment-variables
* envFile を参照 https://code.visualstudio.com/docs/python/settings-reference#_general-python-settings