[Python]フォルダ・ファイルのパスを文字列で取得する方法比較（os.getcwd()、__file__、os.path.dirname(__file__)）

[以前の記事](https://qiita.com/V_lasergun/items/82f9e513c42dbf20937f)を書いたとき、うまく動作しないPythonスクリプトがあった。
ファイル名の取得方法が誤っているのだろうと推定し、修正することで狙い通りの動作をするようになった。
その不具合・正常動作の理由を実験で確認したのでメモしておく。

[要点](#要点)
[実験](#実験)
[Python 3.8.10 の場合](#python-3810-の場合)
[Python 3.8.10 1つ上のディレクトリから実行](#python-3810-1つ上のディレクトリから実行)
[Python 3.8.10 まとめ](#python-3810-まとめ)
[Python 3.10.6 の場合](#python-3106-の場合)
[Python 3.10.6 1つ上のディレクトリから実行](#python-3106-1つ上のディレクトリから実行)
[Python 3.10.6 まとめ](#python-3106-まとめ)
[パス文字列の結合](#パス文字列の結合)
[おまけ1](#おまけ1)
[おまけ2](#おまけ2)
[参考](#参考)

# 要点

- `os.getcwd()`か`__file__`でパスを取得。
- `os.path.join()`でパスを結合。
- `__file__`の仕様がPython3.8以前・Python3.9以降で変更されている。

# 実験

以下のスクリプトを実行し、出力結果を比較する。

```python:get_names.py
import os

print(os.getcwd())
print(__file__)
print(os.path.dirname(__file__))
```

## Python 3.8.10 の場合

[以前の記事](https://qiita.com/V_lasergun/items/82f9e513c42dbf20937f)を書いた時点で入っていたPythonのバージョンがこれだった。

```
> python C:\Users\（略）\test\get_names.py
C:\Users\（略）\test
C:\Users\（略）\test\get_names.py
C:\Users\（略）\test
```

```
> python get_names.py
C:\Users\（略）\test
get_names.py
                （※空行が出力される）
```

```
> python .\get_names.py
C:\Users\（略）\test
.\get_names.py
.
```

いかにも不具合の種になりそうな差異が見られる。

### Python 3.8.10 1つ上のディレクトリから実行

別ディレクトリから実行するとどうなるか。

```
> python C:\Users\（略）\test\get_names.py
C:\Users\（略）
C:\Users\（略）\test\get_names.py
C:\Users\（略）\test
```

```
> python test\get_names.py  
C:\Users\（略）
test\get_names.py
test
```

```
> python .\test\get_names.py
C:\Users\（略）
.\test\get_names.py
.\test
```
### Python 3.8.10 まとめ

- `os.getcwd()`は実行時のカレントディレクトリを取得。
- `__file__`には実行時に指定した文字列がそのまま格納されている。
- `os.path.dirname(__file__)`は`__file__`から最後のファイル名を除いた部分を取得する。

## Python 3.10.6 の場合

執筆時点での最新版。

```
> python C:\Users\（略）\test\get_names.py
C:\Users\（略）\test
C:\Users\（略）\test\get_names.py
C:\Users\（略）\test
```

```
> python get_names.py
C:\Users\（略）\test
C:\Users\（略）\test\get_names.py
C:\Users\（略）\test
```

```
> python .\get_names.py
C:\Users\（略）\test
C:\Users\（略）\test\get_names.py
C:\Users\（略）\test
```

### Python 3.10.6 1つ上のディレクトリから実行

同様に比較。

```
> python C:\Users\（略）\test\get_names.py
C:\Users\（略）
C:\Users\（略）\test\get_names.py
C:\Users\（略）\test
```

```
> python test\get_names.py  
C:\Users\（略）
C:\Users\（略）\test\get_names.py
C:\Users\（略）\test
```

```
> python .\test\get_names.py
C:\Users\（略）
C:\Users\（略）\test\get_names.py
C:\Users\（略）\test
```

### Python 3.10.6 まとめ

- `__file__`が常にフルパスになった。
- ファイルのあるディレクトリで実行するなら、`os.getcwd()`と`__file__`は同じ文字列になる。

特に理由が無ければPython3.9以降を使うほうが不具合を避けられるだろう。

# パス文字列の結合

パス文字列を結合する際は`os.path.join()`を使う。
OS毎の区切り文字の差異（`/`or`\`）に対応するなど、安全にパス文字列を操作できる。

使用例。
詳細は[以前の記事](https://qiita.com/V_lasergun/items/82f9e513c42dbf20937f)を参照。

```python:use_rs.py
import os
from ctypes import cdll

libfile = "rs_add.dll"
cwd = os.getcwd()
libpath = os.path.join(cwd, libfile)

rslib = cdll.LoadLibrary(libpath)
print(rslib.rs_add(4, 6))
```

# おまけ1

VS Codeの「右クリック → ターミナルでPythonファイルを実行する」の方法で実行すると、コマンドの前に`&`マークが付いていることに気づいた。
PowerShellの機能らしい。

この方法で実行するとドライブ名が小文字になったり、区切り文字が`\`→`/`と変化したりする。
手入力で実行する場合でも同様。
一応注意しておく。

```
> & python c:/Users/（略）/test/get_names.py
C:\Users\（略）\test
c:/Users/（略）/test/get_names.py
c:/Users/（略）/test
```

# おまけ2

`os.path.realpath(__file__)`、`os.path.abspath(__file__)`なんてのもあるらしい。

# 参考

https://note.nkmk.me/python-script-file-path/

https://docs.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/about/about_operators?view=powershell-7.2#call-operator-

https://codechacha.com/ja/python-examples-get-working-directory/