---
title: "Python pathlibの使い方｜Path(__file__).resolve().parentの意味とresolve()・absolute()の違い"
url: "https://www.issoh.co.jp/tech/details/5986/"
published: 2025-03-24
updated: 2026-09-27
categories: ["Python"]
publisher: "株式会社一創"
---

# Python pathlibの使い方｜Path(\_\_file\_\_).resolve().parentの意味とresolve()・absolute()の違い

`Path(__file__).resolve().parent` は「いま実行中のファイルが実際に置かれているディレクトリ」を絶対パスで返す書き方です。カレントディレクトリがどこでも同じフォルダを指すので、設定ファイルやデータを読み込むスクリプトの定番になっています。

ただし、Python 3.9以降は `__file__` 自体が絶対パスなので、`resolve()` が効くのはシンボリックリンク経由の起動と、パスに `..` が混ざるときに限られます。この記事では、その差をPython 3.14.6で実行した出力で示し、`absolute()`・`os.path.abspath()` との違い、`stem`・`suffix`、バージョンで挙動が変わった点を整理します。

## まとめ

- `Path(__file__).resolve().parent` は実行中ファイルの実体があるディレクトリ。2階層上は `.parents[1]`（`.parent.parent` と同じ）
- `resolve()` は絶対パス化に加えて `..` の除去とシンボリックリンクの解決を行う。`absolute()` はカレントディレクトリを前に付けるだけ
- `os.path.abspath()` は `..` を文字列として消すので、リンクが絡むと `resolve()` と結果が食い違う
- REPLとJupyterには `__file__` が無い。PyInstallerでは、実行ファイルの隣の設定は `sys.executable`、バンドル内のデータは `__file__` を基準にする
- 3.13で「`**`」で終わるglobとリンクのループ時の例外、3.14で `suffix` の扱いが変わった。古い記事のコードは版を確認してから使う

フォルダ作成・ファイル作成・削除の手順は[Pythonでフォルダ作成・ファイル作成をする方法｜存在確認・削除・移動をpathlibで書く](/tech/details/3292/)にまとめています。この記事はパスの取り出し方と解決の仕組みを扱います。

## Path(\_\_file\_\_).resolve().parentが返すディレクトリ

チェーンを左から読むと、`Path(__file__)` でファイルのパスをPathオブジェクトにし、`resolve()` で実体の絶対パスに直し、`parent` で1つ上＝ファイルを含むディレクトリを取り出しています。典型的な使い方は、スクリプトと同じ場所にある設定ファイルの読み込みです。

```
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent      # このファイルがあるディレクトリ
ROOT_DIR = Path(__file__).resolve().parents[1]  # 1つ上（parent.parent と同じ）
# このスクリプトと同じディレクトリに、UTF-8の config.toml を用意しておく
config = BASE_DIR / "config.toml"
print(config.read_text(encoding="utf-8"))
```

`open("config.toml")` のように相対パスで書くと、Pythonを起動したカレントディレクトリを基準に探します。cronやIDEの実行設定で起動場所が変わると `FileNotFoundError` になるため、ファイル位置を基準にするのが安全です。

### resolve()を省いて結果が変わるケース

Python 3.9で、コマンドラインで指定したスクリプトの `__file__` が絶対パスになりました（What’s New in Python 3.9）。インポートされたモジュールの `__file__` は3.4から絶対パスです。そのため普通に `python proj/app/main.py` と起動する限り、`Path(__file__).parent` だけでも絶対パスが返ります。

結果が変わるのは、シンボリックリンク経由で起動したときです。`bin/run` を `proj/app/main.py` へのリンクにして、同じスクリプトを2通りに起動しました。

```
# proj/app/main.py
from pathlib import Path

print(Path(__file__))
print(Path(__file__).parent)
print(Path(__file__).resolve().parent)
```

```
$ python proj/app/main.py
/private/tmp/demo/proj/app/main.py
/private/tmp/demo/proj/app
/private/tmp/demo/proj/app
$ python bin/run
/private/tmp/demo/bin/run
/private/tmp/demo/bin
/private/tmp/demo/proj/app
```

リンク経由では `parent` がリンクの置き場所（`bin`）を指し、隣にあるはずの設定ファイルを見失います。`resolve()` を挟んだ行だけが実体の `proj/app` を返しました。スクリプトを `/usr/local/bin` などにリンクして呼び出す運用では省略しないでください。

### parents\[1\]とparent.parentによる2階層上の取得

`parents` は祖先ディレクトリの並びで、`parents[0]` が `parent`、`parents[1]` が `parent.parent` に当たります。3.10からスライスと負のインデックスが使えます。

```
from pathlib import Path, PurePosixPath

p = PurePosixPath("/home/taro/project/src/app/main.py")
print(p.parent)                 # /home/taro/project/src/app
print(p.parent.parent)          # /home/taro/project/src
print(p.parents[1])             # parent.parent と同じ
print(p.parents[-1])            # 3.10以降：負のインデックスで最上位（/）
print(list(p.parents[:3]))      # 3.10以降：スライス
print(PurePosixPath("foo/..").parent)   # 字句的な処理なので foo になる
print(Path("main.py").parent)   # 相対パスの親は "."
```

最後の2行が落とし穴です。`parent` は文字列を区切りで切るだけなので、`foo/..` の親は `foo` になり、相対パス `main.py` の親は `.` のままです。公式ドキュメントも、ファイルシステム上で上へたどるなら先に `resolve()` を呼ぶよう勧めています。

### \_\_file\_\_が使えない環境の代替

対話モード（REPL）、`python -c`、Jupyter Notebookでは `__file__` が定義されず `NameError` になります。PyInstallerで固めた実行ファイルでは、`sys.executable` が実行ファイル自身、`sys._MEIPASS` がバンドルの展開先（one-file形式では一時フォルダ）を指します（PyInstaller 6.22.3のRun-time Information）。実行ファイルの隣に置いた設定を読むなら `sys.executable` 側を基準にします。

```
import sys
from pathlib import Path

def app_dir() -> Path:
    if getattr(sys, "frozen", False):           # PyInstaller などで固めた実行ファイル
        return Path(sys.executable).resolve().parent
    try:
        return Path(__file__).resolve().parent
    except NameError:                           # ファイル位置ではなく現在の作業ディレクトリを使う
        return Path.cwd()

print(app_dir())
```

ファイルとして実行しても、`python -c` に同じコードを渡しても、どちらもディレクトリを返すことを3.14.6で確認しました。

## resolve()・absolute()・os.path.abspath()の違い

3つとも「絶対パスを得る」手段ですが、`..` とシンボリックリンクの扱いが違います。存在しないパスを渡した結果が次のとおりです（macOSで実行）。表示例のカレントディレクトリは /private/tmp/demo で、dataとconfig.tomlが存在しない状態を前提にしています。

```
import os
from pathlib import Path

p = Path("data/../config.toml")        # data と config.toml はまだ無くてもよい
print(p.absolute())                     # .. を残す
print(os.path.abspath(p))               # .. を文字列として消す
print(p.resolve())                      # .. を消し、シンボリックリンクも解決
print(Path("/tmp").resolve())           # macOS では /tmp が /private/tmp へのリンク
try:
    p.resolve(strict=True)
except FileNotFoundError as e:
    print("strict=True:", type(e).__name__)
```

```
/private/tmp/demo/data/../config.toml
/private/tmp/demo/config.toml
/private/tmp/demo/config.toml
/private/tmp
strict=True: FileNotFoundError
```

| 方法                   | ..の除去  | リンク解決 | ファイルに触る |
| -------------------- | ------ | ----- | ------- |
| `Path.absolute()`    | しない    | しない   | 触らない    |
| `os.path.abspath()`  | 文字列で消す | しない   | 触らない    |
| `Path.resolve()`     | する     | する    | 触る      |
| `os.path.realpath()` | する     | する    | 触る      |

`os.path.abspath()` の除去は危険側に倒れます。`data` が別の場所へのリンクだった場合、`data/..` の実体はリンク先の親ですが、`abspath()` は文字列上で打ち消してしまいます。公式ドキュメントの対応表も、`abspath()` はリンクを解決せずに `..` を消すため意味が変わりうると注記しています。pathlibで `..` とシンボリックリンクを解決するなら `resolve()` を使います。

逆に `resolve()` はファイルシステムに問い合わせるので、ネットワークドライブや大量のパスではコストがかかります。表示用に絶対パスへ直すだけなら `absolute()` で足ります。

### 存在しないパスとstrict=True

`resolve()` の既定は `strict=False` で、存在しない部分はそのまま後ろにつなげて返します。これから作るファイルのパスを組み立てるときはこれで困りません。存在を前提に処理するなら `strict=True` にすると、見つからない時点で `FileNotFoundError`（`OSError` の一種）が出ます。

シンボリックリンクがループしている場合の扱いは3.13で変わりました。3.12以前は `strict` の値に関係なく `RuntimeError`、3.13以降は非strictなら例外なしでパスを返し、strictなら `OSError` です。3.10〜3.15.0b4で実行して、この境目を確認しました。`except RuntimeError` でループを捕まえていたコードは、3.13以降では素通りします。

## ファイル名・拡張子の取り出し（name・stem・suffix）

「拡張子なしのファイル名」は `stem`、拡張子は `suffix` です。多重拡張子とドットファイルで結果が直感とずれます。

```
from pathlib import PurePosixPath as P

for s in ["report.csv", "backup.tar.gz", ".bashrc", "draft."]:
    p = P("/srv/files") / s
    print(f"{s:14} name={p.name!r:16} stem={p.stem!r:14} suffix={p.suffix!r:6} suffixes={p.suffixes}")
```

```
report.csv     name='report.csv'     stem='report'       suffix='.csv' suffixes=['.csv']
backup.tar.gz  name='backup.tar.gz'  stem='backup.tar'   suffix='.gz'  suffixes=['.tar', '.gz']
.bashrc        name='.bashrc'        stem='.bashrc'      suffix=''     suffixes=[]
draft.         name='draft.'         stem='draft'        suffix='.'    suffixes=['.']
```

上は3.14.6の出力です。`backup.tar.gz` の `stem` は `backup.tar` なので、`backup` だけが欲しいなら、外す拡張子が決まっている前提で `name.removesuffix(".tar.gz")`（3.9以降）を使います。最初のドットで切る方法は `.bashrc` を空文字にしてしまいます。`.bashrc` はドット始まりでも拡張子扱いされません。末尾がドットだけの `draft.` は、3.13.14では `stem='draft.'`・`suffix=''` でした。3.14から単独の「.」も有効な拡張子として扱われるようになったためで、拡張子の有無で振り分ける処理は版によって結果が変わります。

拡張子の付け替えは `with_suffix(".json")`、名前部分だけの変更は `with_stem("new")`（3.9以降）です。

## パスの結合と相対パスへの変換

結合は `/` 演算子か `joinpath()` です。右側に絶対パスを渡すと左側が捨てられる点に注意してください。ユーザー入力をそのまま結合すると、意図しない場所を指せてしまいます。

```
from pathlib import PurePosixPath as P

base = P("/srv/app")
print(base / "logs" / "app.log")        # /srv/app/logs/app.log
print(base.joinpath("logs", "app.log"))  # 同上
print(base / "/etc/hosts")               # 右側が絶対パスだと左側は捨てられる
print(P("/srv/app/logs/app.log").relative_to(base))            # logs/app.log
print(P("/srv/data/x.csv").relative_to(base, walk_up=True))    # 3.12以降：../data/x.csv
try:
    P("/srv/data/x.csv").relative_to(base)
except ValueError as e:
    print("ValueError")
```

`relative_to()` は既定では基準の配下にないと `ValueError` です。3.12で加わった `walk_up=True` を付けると `..` を使った相対パスを返します。これも文字列上の計算なので、リンクを含む場合は先に両方を `resolve()` しておきます。なお3.12以前に許されていた `relative_to("a", "b")` のような複数引数は、3.14で削除されました。

Windowsのパスは `PureWindowsPath` で、macOSやLinux上でも挙動を確かめられます。

```
from pathlib import PureWindowsPath

p = PureWindowsPath(r"C:\Users\taro\data\sales.csv")
print(p.parent)        # C:\Users\taro\data
print(p.drive, p.stem) # C: sales
print(p.as_posix())    # C:/Users/taro/data/sales.csv
```

## 存在確認・読み書き・検索のメソッド早見表

よく使うメソッドと、引数が増えた版をまとめます。フォルダ作成や削除の詳しい手順は、前掲の「Pythonでフォルダ作成・ファイル作成をする方法」で扱っています。

| 用途      | メソッド                           | 版の注意                   |
| ------- | ------------------------------ | ---------------------- |
| 存在確認    | `exists()`                     | 3.12で follow\_symlinks |
| 種別判定    | `is_file()` `is_dir()`         | 3.13で follow\_symlinks |
| テキスト読込  | `read_text()`                  | 3.13で newline          |
| テキスト書込  | `write_text()`                 | 上書きのみ・3.10で newline    |
| バイナリ    | `read_bytes()` `write_bytes()` | 3.5から                  |
| 空ファイル作成 | `touch()`                      | 既定では内容を保ち時刻を更新         |
| 削除      | `unlink()`                     | 3.8で missing\_ok       |
| 再帰検索    | `rglob()`                      | 3.12で case\_sensitive  |
| ツリー走査   | `walk()`                       | 3.12から                 |
| コピー・移動  | `copy()` `move()`              | 3.14から                 |

`write_text()` には追記モードがありません。追記は `open("a")` を使います。

```
from pathlib import Path

log = Path("app.log")
log.write_text("1行目\n", encoding="utf-8")        # 上書き（ファイルが無ければ作る）
with log.open("a", encoding="utf-8") as f:           # 追記は open("a") を使う
    f.write("2行目\n")
print(log.read_text(encoding="utf-8"), end="")
print(sorted(p.name for p in Path(".").rglob("*.log")))  # サブフォルダまで再帰検索
log.unlink(missing_ok=True)                           # 3.8以降：無くても例外にしない
```

`encoding` を省くとOSのロケール依存の文字コードで読み書きされ、Windowsで日本語が化ける原因になります。明示しておけば環境差が出ません。Python 3.15ではUTF-8モードが既定になるPEP 686が入る予定で、省略時の挙動そのものが版で変わります。ログの世代管理まで必要なら[PythonのRotatingFileHandlerでログローテーション｜maxBytes・backupCountの設定と複数プロセス対策](/tech/details/3696/)も参照してください。

## os.pathとの対応と使い分け

既存コードを読み替えるときの対応です。[公式ドキュメントの対応表](https://docs.python.org/3/library/pathlib.html#corresponding-tools)から、この記事で扱った関数を抜き出しました。

| os / os.path                | pathlib                 |
| --------------------------- | ----------------------- |
| `os.path.dirname(__file__)` | `Path(__file__).parent` |
| `os.path.basename()`        | `.name`                 |
| `os.path.splitext()`        | `.stem` `.suffix`       |
| `os.path.join()`            | `/` `joinpath()`        |
| `os.path.abspath()`         | `absolute()`（..は残る）     |
| `os.path.realpath()`        | `resolve()`             |
| `os.path.relpath()`         | `relative_to()`         |
| `os.getcwd()`               | `Path.cwd()`            |
| `os.path.exists()`          | `exists()`              |

この対応表は完全な互換表ではありません。たとえば `os.path.relpath()` は基準の配下でなくても相対パスを返しますが、既定の `relative_to()` は `ValueError` になります。末尾に区切りがあるパスの `basename()` と `name` なども結果が異なります。`os.path.abspath(os.path.dirname(__file__))` を機械的に `Path(__file__).parent.absolute()` に直すと、`..` が残ります。リンクと `..` を畳みたい元の意図なら `resolve()` に寄せてください。

新規コードはpathlibで書き、`open()`・`shutil`・`subprocess` などにはPathをそのまま渡せます。ただし対応開始版は関数・引数・OSによって異なり、`subprocess` の `args` でPathを使えるのは、`shell=False` の場合、POSIXでは3.6以降、Windowsでは3.8以降です。文字列しか受け付けない古いライブラリに渡すときだけ `str(path)` で変換します。スタイル面の規約は[PEP8とは？Pythonコーディング規約の基本ルールとチェックツール（Ruff対応）](/tech/details/3032/)にまとめています。

## Python 3.10〜3.15で結果が変わるpathlibの挙動

pathlibは毎年のように引数や戻り値が変わっており、本記事で扱うメソッドにも、Pythonのバージョンによって結果が変わるものがあります。3.10.20・3.11.15・3.12.13・3.13.14・3.14.6・3.15.0b4（macOS）で同じコードを実行して確かめた差分です。

| 変更点                         | 変わった版   | 以前の挙動                |
| --------------------------- | ------- | -------------------- |
| parents のスライス・負インデックス       | 3.10    | IndexError・TypeError |
| relative\_to(walk\_up=True) | 3.12    | 引数なし                 |
| walk()・is\_junction()       | 3.12    | メソッドなし               |
| glob(“\*\*”) がファイルも返す       | 3.13    | ディレクトリだけ             |
| リンクのループで resolve()          | 3.13    | 常に RuntimeError      |
| full\_match()・from\_uri()   | 3.13    | メソッドなし               |
| suffix が単独の「.」を認識           | 3.14    | 空文字                  |
| copy()・move()・info          | 3.14    | shutil を使う           |
| relative\_to の複数引数          | 3.14で削除 | 結合して解釈               |
| is\_reserved()              | 3.15で削除 | 3.13から非推奨            |

影響が大きいのは `glob("**")` です。同じ `g` フォルダ（`g/sub/f.txt` と `g/top.py` を配置）に対して、3.12.13は `['g', 'g/sub']`、3.13.14は `['g', 'g/sub', 'g/sub/f.txt', 'g/top.py']` を返しました。ディレクトリだけを列挙するつもりで「`**`」を使っていたコードは、3.13に上げるとファイルまで処理対象になります。ディレクトリに限るなら公式どおり末尾に区切りを付けて `glob("**/")` と書きます。

サポート期間の面では、3.9は2025年10月に終了し、3.10も2026年10月ごろにサポート終了予定です（PEP 619）。3.15.0の正式版は2026年10月1日の予定です（PEP 790）。この表の3.12以降の機能を使うなら、実行環境の版を先に揃えてください。各版の変更全体は[Python 3.13の新機能まとめ｜JIT・フリースレッド・対話型シェルの要点と3.14との違い【2026年版】](/tech/details/4042/)と[Python 3.14の新機能まとめ｜JITコンパイラ・フリースレッド版(GIL撤廃)・t-stringを最新版で解説](/tech/details/9409/)で解説しています。

## よくある質問

### Path(\_\_file\_\_).resolve().parentとPath(\_\_file\_\_).parentはどちらを使うべきですか？

resolve()付きを使ってください。Python 3.9以降はどちらも絶対パスを返しますが、シンボリックリンク経由で起動されるとparentだけではリンクの置き場所を指し、実体のディレクトリを取れません。

### pathlibで2つ上のディレクトリを取るには？

Path(\_\_file\_\_).resolve().parents\[1\]、またはparent.parentです。どちらも同じパスを返します。parentsは3.10以降ならparents\[-1\]で最上位も取れます。

### resolve()とabsolute()の違いは何ですか？

absolute()はカレントディレクトリを前に付けるだけで、..もシンボリックリンクもそのまま残します。resolve()はファイルシステムに問い合わせて..を除去し、リンクを実体に置き換えます。

### 拡張子なしのファイル名を取るには？

stem属性です。ただしbackup.tar.gzのstemはbackup.tarです。既知の多重拡張子.tar.gzを外すならname.removesuffix(“.tar.gz”)を使います。最初のドットで区切る処理は、.bashrcを空文字にし、名前に含まれるドット以降も削除するため、汎用的な拡張子除去には使えません。

### pathlibはpip installが必要ですか？

不要です。Python 3.4から標準ライブラリに含まれています。PyPIにある同名パッケージは旧版のバックポートで、入れる必要はありません。

## 関連記事

- [Pythonでフォルダ作成・ファイル作成をする方法｜存在確認・削除・移動をpathlibで書く](/tech/details/3292/)
- [Python 3.14の新機能まとめ｜JITコンパイラ・フリースレッド版(GIL撤廃)・t-stringを最新版で解説](/tech/details/9409/)
- [Python 3.13の新機能まとめ｜JIT・フリースレッド・対話型シェルの要点と3.14との違い【2026年版】](/tech/details/4042/)
- [PEP8とは？Pythonコーディング規約の基本ルールとチェックツール（Ruff対応）](/tech/details/3032/)
- [PythonのRotatingFileHandlerでログローテーション｜maxBytes・backupCountの設定と複数プロセス対策](/tech/details/3696/)

---

出典: [Python pathlibの使い方｜Path(\_\_file\_\_).resolve().parentの意味とresolve()・absolute()の違い](<https://www.issoh.co.jp/tech/details/5986/>)（株式会社一創）
