ファイルやフォルダのパス操作は、どのPythonプロジェクトでも避けて通れません。
pathlibは、OS差を意識せず直感的にパスを扱える標準ライブラリです。
本記事ではPath
の使い方を、パスの結合、ディレクトリ走査、存在確認、基本的なファイル操作まで順にやさしく解説します。
Python初心者の方でも今日から実務で使えるようになることを目指します。
pathlibとは?Pythonでモダンなパス操作
Pathの基本
pathlib
はPython標準ライブラリで、パス文字列を「文字列」ではなく「オブジェクト」として扱います。
中心となるクラスがPath
です。
スラッシュ/
演算子で自然に連結でき、パスの各部分に簡単にアクセスできます。
# pathlibの基本: Pathオブジェクトを作り、結合や分解をしてみます
from pathlib import Path
# 相対パスをオブジェクトで表現
p = Path("data") / "images" / "logo.png"
print(p) # OS依存の見た目で表示されます
print(p.parts) # パスを各要素に分解
print(p.name) # ファイル名部分
print(p.suffix) # 拡張子
print(p.stem) # 拡張子を除いた名前
出力例(POSIX系 macOS/Linux)
data/images/logo.png
('data', 'images', 'logo.png')
logo.png
.png
logo
出力例(Windows)
data\images\logo.png
('data', 'images', 'logo.png')
logo.png
.png
logo
カレント/ホームディレクトリへアクセス
今いるディレクトリやホームディレクトリには、メソッド1つでアクセスできます。
from pathlib import Path
cwd = Path.cwd() # カレントディレクトリ
home = Path.home() # ホームディレクトリ
print("cwd:", cwd)
print("home:", home)
cwd: /Users/alice/projects/myapp
home: /Users/alice
OS差を意識しないパス表現
PathはOSに合わせて区切り文字やドライブ表現を自動調整します。
コード上では/
演算子で結合すればOKです。
from pathlib import Path
# OS非依存に結合できる
conf = Path("config") / "app.ini"
print(conf) # Windowsなら \ 区切り、Linux/Macなら / 区切りで表示
config/app.ini # macOS/Linux
# または
config\app.ini # Windows
Windowsの手打ちパスで\n
や\t
がエスケープとして解釈されることがあります。
文字列リテラルにWindowsパスを書くなら、次のいずれかを使います。
from pathlib import Path
# NG: \n が改行として解釈される
bad = Path("C:\new\notes.txt")
# OK: raw文字列を使う
good1 = Path(r"C:\new\notes.txt")
# OK: スラッシュに統一してもWindowsで正しく解釈される
good2 = Path("C:/new/notes.txt")
print(good1)
print(good2)
Pathとos.pathの違い
Pathはオブジェクト指向・直感的、os.pathは関数指向・文字列中心です。
どちらも標準ですが、これから新規で書くならpathlibが推奨です。
多くの標準関数はPathをそのまま受け取れるため、相互運用も問題ありません。
次の表は、よく使う操作の対応関係です。
やりたいこと | os.path系 | pathlib系 |
---|---|---|
パス結合 | os.path.join(a, b) | Path(a) / b |
親ディレクトリ | os.path.dirname(p) | Path(p).parent |
ベース名 | os.path.basename(p) | Path(p).name |
拡張子 | os.path.splitext(p)[1] | Path(p).suffix / suffixes |
拡張子除いた名 | os.path.splitext(p)[0] | Path(p).stem |
存在確認 | os.path.exists(p) | Path(p).exists() |
ディレクトリ作成 | os.makedirs(p, exist_ok=True) | Path(p).mkdir(parents=True, exist_ok=True) |
中身の列挙 | os.listdir(p) | Path(p).iterdir() |
ワイルドカード | glob.glob(pattern) | Path(p).glob(pattern) / rglob(pattern) |
ファイルを開く | open(p, mode=…) | Path(p).open(mode=…) |
相互運用の例です。
多くの組み込み関数はPathオブジェクトをそのまま受け取れます。
from pathlib import Path
import json
path = Path("settings.json")
# Path.open で書く
with path.open("w", encoding="utf-8") as f:
json.dump({"debug": True}, f, ensure_ascii=False, indent=2)
# 組み込み open に Path を渡してもOK
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
print(data)
{'debug': True}
Pathでパス結合の基本
/演算子で直感的にパス結合
スラッシュ/
演算子でパスをつなげます。
可読性が高く、引数順を間違えにくいのが利点です。
from pathlib import Path
root = Path("project")
p1 = root / "src" / "main.py"
p2 = root / "README.md"
print(p1)
print(p2)
project/src/main.py
project/README.md
右側が絶対パスの場合、左側は破棄されます。
これはOS共通の仕様です。
from pathlib import Path
print(Path("/etc") / "hosts") # => /etc/hosts
print(Path("/etc") / Path("/var")) # => /var (右が絶対パスなので左は捨てられる)
/etc/hosts
/var
相対パスと絶対パス
相対パスは現在のカレントディレクトリ基準で解釈され、絶対パスはドライブやルートから始まります。
from pathlib import Path
rel = Path("logs/app.log")
abs1 = Path("/var/log/system.log") # POSIXの例
print("rel is absolute?", rel.is_absolute())
print("abs1 is absolute?", abs1.is_absolute())
# 実体の有無にかかわらず、絶対パスへ解決(シンボリックリンクの解決は環境次第)
print("resolved rel:", rel.resolve())
rel is absolute? False
abs1 is absolute? True
resolved rel: /Users/alice/projects/myapp/logs/app.log
resolve()
は既定ではstrict=False
です。
存在しないパスも「計算上の絶対パス」に展開します。
実在チェックはexists()
で行います。
name/suffix/stemでファイル名と拡張子を取得
ファイル名や拡張子の取得・変更がメソッドやプロパティで簡単にできます。
from pathlib import Path
p = Path("/path/to/archive.tar.gz")
print("name:", p.name) # archive.tar.gz
print("suffix:", p.suffix) # .gz
print("suffixes:", p.suffixes) # ['.tar', '.gz']
print("stem:", p.stem) # archive.tar
# 拡張子の置き換え
print("zip:", p.with_suffix(".zip"))
name: archive.tar.gz
suffix: .gz
suffixes: ['.tar', '.gz']
stem: archive.tar
zip: /path/to/archive.tar.zip
parent/parentsで親ディレクトリをたどる
parent
で1つ上、parents
で上位を順に取得します。
from pathlib import Path
p = Path("/usr/local/bin/python3")
print("parent:", p.parent) # /usr/local/bin
# 祖先ディレクトリを上から順に
for i, anc in enumerate(p.parents, start=1):
print(f"{i}: {anc}")
parent: /usr/local/bin
1: /usr/local
2: /usr
3: /
ディレクトリ走査と検索
iterdirでフォルダ内を走査
iterdir()で直下のエントリを列挙できます。
種類で分岐するのも簡単です。
from pathlib import Path
base = Path.cwd() # 今いるディレクトリ
for p in sorted(base.iterdir()):
kind = "DIR " if p.is_dir() else ("FILE" if p.is_file() else "OTHER")
print(kind, p.name)
出力例(あなたの環境で異なります):
DIR .git
FILE README.md
DIR src
FILE pyproject.toml
OTHER .DS_Store
globで拡張子やワイルドカード検索
glob()
はワイルドカード検索ができます。
直下のみが対象です。
from pathlib import Path
for p in Path.cwd().glob("*.py"):
print(p.name)
main.py
utils.py
test_main.py
パターン例は*.txt
、data_??.csv
、[ab]*.md
などが使えます。
rglobで再帰的に走査
サブディレクトリも含めて検索したいときはrglob()
を使います。
from pathlib import Path
# プロジェクト内の全ての .py を再帰的に列挙
for p in Path.cwd().rglob("*.py"):
print(p)
/Users/alice/projects/myapp/main.py
/Users/alice/projects/myapp/src/utils/io.py
/Users/alice/projects/myapp/tests/test_main.py
存在確認と基本のファイル操作
pathlibだけで作成・読み書き・移動・削除まで一通りの操作が完結します。
以下の例では、tempfile.TemporaryDirectory
を使って一時ディレクトリ内で安全に操作します。
exists/is_file/is_dirで存在と種類を確認
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as tmp:
base = Path(tmp)
f = base / "hello.txt"
d = base / "docs"
print("初期:", f.exists(), f.is_file(), d.is_dir())
# 作成
d.mkdir()
f.write_text("hello", encoding="utf-8")
print("作成後:", f.exists(), f.is_file(), d.is_dir())
初期: False False False
作成後: True True True
mkdirでディレクトリ作成
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as tmp:
deep = Path(tmp) / "a" / "b" / "c"
# 親が無くてもまとめて作る
deep.mkdir(parents=True, exist_ok=True)
print("作成できたか:", deep.exists(), "親:", deep.parent)
作成できたか: True 親: /tmp/tmpxxxxxx/a/b
既に存在していてもエラーにしたくない場合はexist_ok=True
を付けます。
read_text/write_textでテキストを読み書き
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as tmp:
f = Path(tmp) / "note.txt"
# 文字コードは明示するのが安全(UTF-8推奨)
f.write_text("1行目\n2行目", encoding="utf-8")
content = f.read_text(encoding="utf-8")
print(content)
1行目
2行目
バイナリはwrite_bytes()
とread_bytes()
が使えます。
rename/replaceでリネーム
renameは通常の移動・改名、replaceは上書き前提の移動です。
ターゲットが既に存在する場合、rename()
は失敗することがありますが、replace()
は置き換えます。
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as tmp:
base = Path(tmp)
src = base / "a.txt"
dst = base / "b.txt"
src.write_text("A", encoding="utf-8")
# 通常のリネーム
src.rename(dst)
print("dst exists after rename:", dst.exists())
# replace は既存ファイルを上書きする
tmp2 = base / "tmp.txt"
tmp2.write_text("NEW", encoding="utf-8")
dst.write_text("OLD", encoding="utf-8") # 既にある状態
tmp2.replace(dst) # b.txt を NEW で置き換える
print("dst content:", dst.read_text(encoding="utf-8"))
dst exists after rename: True
dst content: NEW
unlinkでファイル削除
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as tmp:
f = Path(tmp) / "remove_me.txt"
f.write_text("bye", encoding="utf-8")
f.unlink() # 削除
print("exists after unlink:", f.exists())
# 無いものを削除してもエラーにしたくない場合
f.unlink(missing_ok=True)
print("missing_okでもエラーにならない")
exists after unlink: False
missing_okでもエラーにならない
ディレクトリを消すときはrmdir()
ですが、空でないと失敗します。
再帰的に消すならshutil.rmtree()
を使います。
Path.openでファイルを開く
with文と組み合わせて安全に読み書きできます。
改行コードを制御したいときはnewline=""
を指定します。
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as tmp:
path = Path(tmp) / "data.csv"
# 書き込み: UTF-8で、改行はそのまま扱う(newline="")
with path.open("w", encoding="utf-8", newline="") as f:
f.write("id,name\n")
f.write("1,Alice\n")
f.write("2,Bob\n")
# 読み込み
with path.open("r", encoding="utf-8") as f:
lines = [line.strip() for line in f]
print(lines)
['id,name', '1,Alice', '2,Bob']
まとめ
本記事では標準ライブラリpathlibを使ったモダンなパス操作を、基本から一通り紹介しました。
PathはOS差を意識せずスラッシュ演算子で直感的に結合でき、存在確認や走査、読み書きまで一貫したAPIで扱えるのが長所です。
従来のos.path
よりも可読性と保守性が向上し、初心者でも誤りを減らしやすくなります。
まずは日々のスクリプトでPath
を使い始め、iterdir()
やglob()
、mkdir()
、read_text()
などを実際に動かしてみてください。
「パスはオブジェクトで扱う」という発想に慣れるだけで、ファイル操作が驚くほど快適になります。