Pythonの上位ディレクトリpath取得についてのメモ

Pythonの自作モジュールにてパス取得にハマってしまったのでメモがてらここに

#環境
MacOS 10.14.6
プロセッサ 2.4 GHz Intel Core i5
RAM 8 GB 2133 MHz 

#ゴール
以下のようなディレクトリ構成において、実行しているファイル（ここでは、`audrey.py`とする）から、srcディレクトリ内のモジュールおよびdataディレクトリ内のファイルを`import`する。
下記のように、dataディレクトリからもsrcディレクトリからも、データをいつでも引っ張れるようにするのが目標。

```python
~/
|
├─ notebook
|    └── audrey.py
|
├─ data
|    └── raw
|         └── wakabayashi.csv
|
└──scr
     ├── __init__.py
     └── kasuga
            ├── __init__.py
            ├── onigawara.py
            └── apaaaaaaa.py
```

メインプログラムの`audrey.py`の中はざっくりとこんな感じ。

```python:audrey.py
from src import *
# here, 'path_raw' is the relative path for loading the raw data
raws = pd.load_csv(path_raw + "wakabayashi.csv")
```

この相対パスは、srcディレクトリ内の`__init__.py`でこんな感じに定義している。
srcディレクトリのインポートに成功し、`__init__.py`が実行されると`Success to import src!`tofu
という出力が得られるようになっている。

```python:src/__init__.py
print("Success to import src!")
path_raw = '../data/row/'
from src.kasuga import onigawara, apaaaaaaa
```

そして、kasugaディレクトリ内の`__init__.py`の中身はこんな感じ。
これもまた、ちゃんとインポートできているかどうかの確認用の`print`文。


```python:src/kasuga/__init__.py
print("Success to import kasuga!")
```

#行けそうで行けない例
これで単純に終わるのでは？とやってみたものの失敗した例。

```python:audrey.py
from ..src import *
# >>> ValueError: attempted relative import beyond top-level package
```

このエラーメッセージを読むと、どうやらPythonでは現在の実行しているファイル（ここでは`audrey.py`）をrootとみなすようで、それより上の階層に対しては参照できないようだ。

#解決策（相対パス、絶対パス）
ここでは、`sys.path`に親のパスを相対パス、絶対パスの２種類で追加するようにトライしたものをメモする。
##相対パスを使用した場合

**`sys.path`にsrcディレクトリを登録**

```python:audrey.py
import sys
sys.path.append('../src/')
from src import kasuga
# >>> ModuleNotFoundError: No module named 'src'
import kasuga
Success to import kasuga!
```
このやり方だと、srcディレクトリ内部の`/kasuga/__init__.py`は実行されるものの、肝心の`/src/__init.py`は実行されない。
`sys.path`の中身はというと、そのまま`'../src/'`が入っているようだ。

```python
print(sys.path)
# >>> ['~/notebook', '../src/']
```
  
  

**`sys.path`に親ディレクトリ（ここでは`~/`に該当）を登録**

今度は、notebookディレクトリ、srcディレクトリの親ディレクトリに該当する部分を相対パスで登録してやろうという。

```python:audrey.py
import sys
sys.path.append('../../~/')
from src import kasuga
# >>> Success to import src!
# >>> Success to import kasuga!
```

```python
print(sys.path)
# >>> ['~/notebook', '../../~/']
```

このやり方だと、`/src/__init__.py`と/src/kasuga/__init__.py`のどちらも問題なく実行された。
`sys.path`はというと、先ほどの例と同じように、文字列`'../../~/'`がそのまま入っている。
ただ、個人的には可読性が低くあまり好きでは無いかなー。

###絶対パスを使用した場合
絶対パスを使用しても、もちろん可能である。
あまり、個人的には絶対パスを使用する頻度は高くないなぁ、というのが所感ではある。
ただ、ディレクトリをバシッと指定するため、非常にわかりやすい。

絶対パスの取得には、`pathlib`が使い勝手が良い（Python 3.4以降）。
以下のコマンドで`pathlib.Path`メソッドを`import`。

```python:audrey.py
import sys
from pathlib import Path
```

```python:audrey.py
# absolute current directory will append into 'sys.path'
sys.path.append(str(Path('__file__').resolve().parent))
# >>> /Users/~~~/~/notebook/

# >>> absolute current PARENT-directory will append into 'sys.path'
sys.path.append(str(Path('__file__').resolve().parent.parent))
# >>> /Users/~~~/~/
```

上のように、`Path('current script name').resolve().parent`で現在実行しているスクリプトのディレクトリを得ることができる。
そして、`Path('current script name').resolve().parent.parent`のように`.parent`を２回重ねることで、その親ディレクトリの絶対パスを取得可能。
ちなみに、ここで`__file__`は、カレントディレクトリからの実行スクリプトまでの相対パスを取得（スクリプト実行直後であれば、実行スクリプト名；e.g. audrey.py が入る）するものであるため、スクリプト名が変更されようとこのコードは問題なく使用することができる。

絶対パスを取得した後であれば、以下のようにしてsrcディレクトリ内のモジュールを`import`可能。

```python:audrey.py
sys.path.append(str(Path('__file__').resolve().parent.parent))
# >>> /Users/~~~/~/
from src import *
# >>> Success to import src!
# >>> Success to import kasuga!
```

ちなみに、`/src/__init__.py`内部で`/data/raw/`への相対パスを`path_raw`という変数に格納しているが、それはsrcを`import`した後であれば`audrey.py`上から呼び出すことが可能。

```python:audrey.py
import sys
from pathlib import Path
sys.path.append(str(Path('__file__').resolve().parent.parent))
from src import *

import pandas as pd
tukkomi_god = pd.read_csv(path_raw + 'wakabayashi.csv')
print(tukkomi_god.radio)
```

```python:audrey.py-outputs
# >>> Success to import src!
# >>> Success to import kasuga!
# >>> http://www.allnightnippon.com/kw/
```
#終わりに
絶望的に私は面倒臭がりなんで、なんでも楽できるようにしたい。それで、こんなコードを書きました。
ディレクトリの相対位置関係が変化しない限り使い続けられるし、そのパス取得には実行スクリプトの外の__init__.pyを使用しているのでそれをimportする限りはプログラムの仕様を変える必要もない、わざわざ毎回定義する必要がない。楽。

もっとこうすればさらに楽になるよ、など情報を頂ければ嬉しいです。
そしてこれが貯まりに貯まったストックの中からのQiita初投稿。
ここが読みにくいよ、などあれば教えていただければ幸いにございます。

いつもQiitaの皆さんの投稿に助けられているので、この記事が誰かの助けにならんことを。

バーイ。