Python pathlibのrelative_to()とresolve()|絶対パスと相対パスの変換、absolute()との違い・ValueErrorの対処

Pythonで絶対パスを相対パスに直すには Path.relative_to(基準のパス)、相対パスを絶対パスに直すには Path.resolve() を使います。基準のフォルダの外にあるパスを「../」付きで表したいときは、Python 3.12以降で relative_to(基準, walk_up=True) と書きます。

この記事では、検索の多い relative_to() と resolve() を中心に、absolute() との違い、ValueError になる理由と対処、os.path.relpath() との違い、ユーザーが入力したパスがフォルダの外を指していないかの確かめ方までを、実行結果つきで説明します。コードはPython 3.14.8で実行し、仕様はpathlibの公式ドキュメント(Python 3.15版、2026年10月15日確認)で確かめました。

早見表:変換と判定に使うメソッド

やりたいこと書き方ファイルを見に行くか
絶対パスを、あるフォルダからの相対パスにするp.relative_to(base)見ない(文字列だけで計算)
フォルダの外でも「../」で相対パスにする(3.12以降)p.relative_to(base, walk_up=True)見ない
相対パスを絶対パスにする(..とリンクも整理)p.resolve()見る(シンボリックリンクをたどる)
相対パスの前に現在のフォルダを付けるだけp.absolute()見ない
絶対パスかどうか判定するp.is_absolute()見ない
あるフォルダの中のパスか判定する(3.9以降)p.is_relative_to(base)見ない

「ファイルを見に行くか」の列が大事です。relative_to() と is_relative_to() は文字列として比べるだけなので、.. やシンボリックリンクがあると、実際の場所と違う答えを返すことがあります。その場合は先に resolve() してから使います。

relative_to():絶対パスを相対パスに変換する

relative_to() は「基準のパスから見て、このパスはどこか」を返します。結果は文字列ではなく Path オブジェクトです。

from pathlib import Path

p = Path("/home/user/project/src/main.py")
print(p.relative_to("/home/user/project"))
print(p.relative_to("/home/user/project/src"))
print(repr(p.relative_to("/home/user")))
src/main.py
main.py
PosixPath('project/src/main.py')

基準を変えると、結果の長さが変わります。ファイルの一覧を「プロジェクトのルートからのパス」で表示したり、別のフォルダへ同じ構成でコピーしたりするときによく使います。文字列が必要なら str() で囲むか、区切りを「/」にそろえたいときは .as_posix() を使います。

ValueError「is not in the subpath of」が出る理由

relative_to() は、基準のフォルダの中にあるパスしか変換できません。外にあるパスを渡すと ValueError になります。

from pathlib import Path

p = Path("/home/user/project/src/main.py")
try:
    p.relative_to("/home/user/docs")
except ValueError as e:
    print("ValueError:", e)

# Python 3.12以降:walk_up=True で「..」を使った相対パスにできる
print(p.relative_to("/home/user/docs", walk_up=True))

# 先に入っているか調べる(3.9以降)
print(p.is_relative_to("/home/user/project"), p.is_relative_to("/home/user/docs"))
ValueError: '/home/user/project/src/main.py' is not in the subpath of '/home/user/docs'
../project/src/main.py
True False

対処は2つあります。1つは、Python 3.12で追加された walk_up=True を付けて「../」で上にさかのぼる相対パスを作る方法です。もう1つは、is_relative_to() で中にあるかを先に調べ、外にあるときは別の処理をする方法です。walk_up は3.11以前では使えず、TypeError: PurePath.relative_to() got an unexpected keyword argument 'walk_up' になります(Python 3.11で確認)。

次の場合も ValueError になります。

from pathlib import Path

# 相対パスと絶対パスは比べられない
try:
    Path("src/main.py").relative_to("/home/user/project")
except ValueError as e:
    print("ValueError:", e)

# 文字列として比べるだけなので「..」で外に出ていても True になる
p = Path("/home/user/project/../secret/key.txt")
print(p.is_relative_to("/home/user/project"))
ValueError: 'src/main.py' is not in the subpath of '/home/user/project'
True

1つ目は、相対パスと絶対パスを比べたためです。片方が相対パスならどちらも resolve() で絶対パスにそろえてから比べます。2つ目は「ValueErrorにならないのに答えがおかしい」例です。/home/user/project/../secret/key.txt は実際には project の外ですが、文字列の先頭が一致するので True になります。公式ドキュメントも、この方法は文字列だけで計算し、.. を特別扱いしないと説明しています。

なお、Python 3.11以前のエラーメッセージは「… is not in the subpath of … OR one path is relative and the other is absolute.」という長い形でした。3.14では後半が付かない短い形になっています。

resolve()とabsolute()の違い:相対パスを絶対パスに変換する

どちらも絶対パスを返しますが、中身が違います。absolute() は現在のフォルダ(カレントディレクトリ)を前に付けるだけで、.. もシンボリックリンクもそのまま残します。resolve() は .. を取り除き、シンボリックリンクを実際の場所までたどります。公式ドキュメントでは、.. を取り除けるのは resolve() だけだと説明されています。

次の例では、work/project の中に、../shared を指すシンボリックリンク conf を置いています(出力の作業フォルダの部分は /Users/you/work に置き換えて表示しています)。

import os
from pathlib import Path

os.chdir("work/project")                 # 作業フォルダを project にする
print("cwd      :", Path.cwd())

p = Path("../project/data/./sample.txt")
print("absolute :", p.absolute())         # 前に cwd を付けるだけ
print("resolve  :", p.resolve())          # .. と . を整理し、リンクもたどる

link = Path("conf/config.ini")            # conf は ../shared へのシンボリックリンク
print("absolute :", link.absolute())
print("resolve  :", link.resolve())

# 存在しないパスでも resolve() はエラーにならない(strict=False が既定)
print("resolve  :", Path("nothing/here.txt").resolve())
try:
    Path("nothing/here.txt").resolve(strict=True)
except FileNotFoundError as e:
    print("strict=True:", type(e).__name__)
cwd      : /Users/you/work/project
absolute : /Users/you/work/project/../project/data/sample.txt
resolve  : /Users/you/work/project/data/sample.txt
absolute : /Users/you/work/project/conf/config.ini
resolve  : /Users/you/work/shared/config.ini
resolve  : /Users/you/work/project/nothing/here.txt
strict=True: FileNotFoundError
absolute()resolve()
.. の整理しないする
シンボリックリンクたどらない実際の場所までたどる
存在しないパスそのまま返す既定ではそのまま返す。strict=True なら FileNotFoundError
向いている場面ログに出すなど、見た目だけ絶対パスにしたいとき2つのパスを比べる、同じファイルか判定する、フォルダの外か確かめるとき

./(現在のフォルダを表す「.」)は、Path を作った時点で取り除かれるので、absolute() の結果にも残っていません。残るのは .. だけです。また、Python 3.13からは、シンボリックリンクが循環しているときの resolve() の動きが変わり、strict=True なら OSError、既定(strict=False)なら例外を出さなくなりました(3.12までは常に RuntimeError)。

is_absolute():絶対パスかどうかの判定

is_absolute() は、パスがルート(「/」など)から始まっているかを返します。Windowsでは、ドライブ名(C:)とルートの両方がそろって初めて絶対パスになります。Windowsのパスは、Macや Linux でも PureWindowsPath で試せます。

from pathlib import PureWindowsPath

print(PureWindowsPath("C:/Users/me/file.txt").is_absolute())
print(PureWindowsPath("/Users/me/file.txt").is_absolute())    # ドライブがない
print(PureWindowsPath(r"C:\Users\me\Documents\a.xlsx").relative_to(r"C:\Users\me"))
print(PureWindowsPath(r"C:\USERS\ME\a.xlsx").relative_to(r"c:\users\me"))  # 大文字小文字は区別しない
try:
    PureWindowsPath(r"D:\data\a.csv").relative_to(r"C:\Users\me", walk_up=True)
except ValueError as e:
    print("ValueError:", e)
True
False
Documents\a.xlsx
a.xlsx
ValueError: 'D:\\data\\a.csv' and 'C:\\Users\\me' have different anchors

Windowsでは大文字と小文字を区別せずに比べます。ドライブが違うパスどうしは、walk_up=True を付けても相対パスにできず、「have different anchors」という ValueError になります。

os.path.relpath()との違い

昔からある os.path.relpath() も相対パスを返します。relpath() は最初から「../」を使ってさかのぼるので、relative_to(..., walk_up=True) と同じ結果になります。違いは、返り値が文字列であることと、基準を省略すると現在のフォルダが基準になることです。

import os
from pathlib import Path

a = "/home/user/project/src/main.py"
b = "/home/user/docs"
print(os.path.relpath(a, b))
print(Path(a).relative_to(b, walk_up=True))

# 現在のフォルダからの相対パス(relpathは第2引数を省略するとcwd基準)
print(os.path.relpath(Path.cwd() / "data" / "x.csv"))
print((Path.cwd() / "data" / "x.csv").relative_to(Path.cwd()))
../project/src/main.py
../project/src/main.py
data/x.csv
data/x.csv

Python 3.11以前でも動かす必要があり、フォルダの外へのパスも扱いたいなら os.path.relpath() が手軽です。それ以外は、結果をそのまま / でつなげる Path で受け取れる relative_to() を使うと、コードを pathlib にそろえられます。

実用例1:スクリプトの場所を基準にファイルを開く

「data/sample.txt を開け」と相対パスで書くと、どのフォルダから python を実行したかで開く場所が変わります。スクリプト自身の場所(__file__)を resolve() で絶対パスにし、そこから組み立てると、どこから実行しても同じファイルを指します。

from pathlib import Path

HERE = Path(__file__).resolve().parent       # このファイルがあるフォルダ
ROOT = HERE.parent                            # プロジェクトのルート
data_file = ROOT / "data" / "sample.txt"

print("cwd       :", Path.cwd())
print("data_file :", data_file)
print("exists    :", data_file.exists())
print("rootから  :", data_file.relative_to(ROOT))

project の1つ上のフォルダ(work)から python project/src/main.py と実行した結果です。

cwd       : /Users/you/work
data_file : /Users/you/work/project/data/sample.txt
exists    : True
rootから  : data/sample.txt

現在のフォルダが work でも、data_file は正しく project の中を指しています。現在のフォルダの確かめ方や移動の方法はPythonでカレントディレクトリを取得・移動する方法で説明しています。

実用例2:入力されたパスがフォルダの外を指していないか確かめる

アップロードされたファイル名やURLの一部を使ってファイルを開く処理では、../ を含む入力で決めたフォルダの外のファイルを読まれないようにする必要があります(パストラバーサルと呼ばれる攻撃です)。先に見たとおり、is_relative_to() だけでは .. を見抜けないので、必ず resolve() してから判定します。

from pathlib import Path

base = Path("work/project").resolve()

def is_inside(user_input):
    """user_input が base の中を指しているときだけ True"""
    target = (base / user_input).resolve()
    return target.is_relative_to(base)

for s in ["data/sample.txt", "../shared/config.ini", "conf/config.ini", "data/../../shared"]:
    print(f"{s:22} -> {is_inside(s)}")
data/sample.txt        -> True
../shared/config.ini   -> False
conf/config.ini        -> False
data/../../shared      -> False

conf/config.ini は文字列だけ見ると project の中ですが、conf が外のフォルダへのシンボリックリンクなので、resolve() した結果は外になり False と判定されました。文字列の比較だけでは、このようなリンク経由の抜け道を防げません。

よくある質問

Q. Pythonで絶対パスを相対パスに変換するには?

pathlibの Path.relative_to(基準のパス) を使います。基準のフォルダの外にあるパスは ValueError になるので、Python 3.12以降なら walk_up=True を付けて「../」付きの相対パスにするか、文字列でよければ os.path.relpath() を使います。

Q. relative_to()で「is not in the subpath of」というエラーが出ます。

変換したいパスが基準のフォルダの中にないか、片方が相対パスでもう片方が絶対パスになっています。両方を resolve() で絶対パスにそろえ、外にある場合は walk_up=True を付けるか、is_relative_to() で先に判定してください。

Q. resolve()とabsolute()の違いは何ですか?

absolute() は現在のフォルダを前に付けるだけで、.. やシンボリックリンクはそのまま残します。resolve() は .. を取り除き、シンボリックリンクを実際の場所までたどった絶対パスを返します。パスどうしを比べるときは resolve() を使います。

Q. resolve()は存在しないファイルでも使えますか?

使えます。既定(strict=False)では、存在しない部分はそのままつなげた絶対パスを返します。存在しないときにエラーにしたい場合は resolve(strict=True) とすると FileNotFoundError になります。

Q. walk_upが使えないと言われます。

relative_to() の walk_up 引数はPython 3.12で追加されました。3.11以前では TypeError になるので、Pythonを更新するか、os.path.relpath() を使ってください。

まとめ

  • 絶対パス→相対パスは relative_to(base)。外にあるパスは walk_up=True(3.12以降)か os.path.relpath()
  • 相対パス→絶対パスは resolve()。absolute() は現在のフォルダを付けるだけで .. もリンクも残る
  • relative_to() と is_relative_to() は文字列だけで計算するので、比べる前に両方を resolve() する
  • スクリプトの場所を基準にするなら Path(__file__).resolve().parent

ファイル名や拡張子の取り出しはpathlibでファイル名・拡張子・親ディレクトリを取得する方法、フォルダの中身の一覧はpathlibでファイル一覧を取得する方法、ファイル操作の全体はPythonのファイル操作まとめで扱っています。

あわせて読みたい

学習の順序はPython学習のロードマップ、標準で使える機能の一覧はPython標準ライブラリのモジュール一覧にまとめています。

プログラミング学習に興味がある方は、まずは無料体験会へ

無料体験会・お申し込みはこちら