はじめに
まだos.path.join()を使っていますか?
Python 3.4以降はpathlibが標準。オブジェクト指向でパスを扱えて、可読性が格段に向上するよ。
# Before (os.path)
import os
path = os.path.join("folder", "subfolder", "file.txt")
basename = os.path.basename(path)
exists = os.path.exists(path)
# After (pathlib)
from pathlib import Path
path = Path("folder") / "subfolder" / "file.txt"
basename = path.name
exists = path.exists()
基本操作
パス作成
from pathlib import Path
# / 演算子でパス結合
p = Path("folder") / "subfolder" / "file.txt"
# folder/subfolder/file.txt (UNIX)
# folder\subfolder\file.txt (Windows)
# 特別なパス
Path.cwd() # 現在のディレクトリ
Path.home() # ホームディレクトリ
パス情報の取得
p = Path("/home/user/documents/report.pdf")
p.name # 'report.pdf'
p.stem # 'report'
p.suffix # '.pdf'
p.parent # Path('/home/user/documents')
p.parts # ('/', 'home', 'user', 'documents', 'report.pdf')
# 複数の拡張子
Path("file.tar.gz").suffixes # ['.tar', '.gz']
パス判定
p = Path("folder/file.txt")
p.exists() # 存在するか
p.is_file() # ファイルか
p.is_dir() # ディレクトリか
p.is_absolute() # 絶対パスか
ファイル操作
読み書き
p = Path("data.txt")
# 書き込み
p.write_text("Hello, pathlib!", encoding="utf-8")
# 読み込み
content = p.read_text(encoding="utf-8")
# バイナリ
p.write_bytes(b"Binary data")
data = p.read_bytes()
ファイル作成・削除
# 空ファイル作成
p.touch()
# 削除
p.unlink()
p.unlink(missing_ok=True) # 存在しなくてもエラーにならない
ディレクトリ操作
作成
# 単一ディレクトリ
Path("folder").mkdir()
# 親ディレクトリも含めて作成
Path("folder/sub1/sub2").mkdir(parents=True, exist_ok=True)
一覧取得
p = Path(".")
# 直下のみ
for item in p.iterdir():
print(item.name, "dir" if item.is_dir() else "file")
削除
Path("empty_folder").rmdir() # 空のディレクトリのみ
# 中身ごと削除はshutil
import shutil
shutil.rmtree(Path("folder"))
glob(パターンマッチ)
p = Path(".")
# 直下のtxtファイル
for f in p.glob("*.txt"):
print(f)
# 再帰的に検索
for f in p.rglob("*.txt"): # **/*.txt と同じ
print(f)
# 複数パターン
for f in p.glob("*.{txt,md}"): # ❌ これは動かない
pass
# 代わりに
for f in p.iterdir():
if f.suffix in [".txt", ".md"]:
print(f)
パス変換
p = Path("folder/file.txt")
p.with_name("other.txt") # folder/other.txt
p.with_suffix(".md") # folder/file.md
p.with_stem("renamed") # folder/renamed.txt (Python 3.9+)
絶対パス・相対パス
# 絶対パスに変換
Path("./folder/../folder/file.txt").resolve()
# → /home/user/project/folder/file.txt
# 相対パスに変換
base = Path("/home/user")
full = Path("/home/user/docs/file.txt")
full.relative_to(base) # docs/file.txt
統計情報
stat = Path("file.txt").stat()
stat.st_size # ファイルサイズ(バイト)
stat.st_mtime # 最終更新時刻(UNIX時間)
stat.st_ctime # 作成時刻
os.path → pathlib 対応表
| os.path | pathlib |
|---|---|
os.path.join(a, b) |
Path(a) / b |
os.path.basename(p) |
p.name |
os.path.dirname(p) |
p.parent |
os.path.splitext(p) |
(p.stem, p.suffix) |
os.path.exists(p) |
p.exists() |
os.path.isfile(p) |
p.is_file() |
os.path.isdir(p) |
p.is_dir() |
os.path.abspath(p) |
p.resolve() |
os.path.expanduser("~") |
Path.home() |
os.getcwd() |
Path.cwd() |
os.listdir(p) |
list(p.iterdir()) |
glob.glob("*.txt") |
Path(".").glob("*.txt") |
実践パターン
特定拡張子のファイルを処理
def process_files(directory: Path, extension: str):
for file_path in directory.rglob(f"*{extension}"):
content = file_path.read_text()
# 処理...
バックアップ作成
import shutil
def backup_file(file_path: Path) -> Path:
backup_path = file_path.with_suffix(file_path.suffix + ".bak")
shutil.copy2(file_path, backup_path)
return backup_path
設定ファイルの場所
def get_config_path() -> Path:
# XDG準拠
config_dir = Path.home() / ".config" / "myapp"
config_dir.mkdir(parents=True, exist_ok=True)
return config_dir / "config.json"
一時ファイル
from pathlib import Path
import tempfile
with tempfile.TemporaryDirectory() as tmpdir:
tmp_path = Path(tmpdir)
(tmp_path / "temp.txt").write_text("Temporary content")
# スコープを抜けると自動削除
型ヒント
from pathlib import Path
def process(file_path: Path) -> None:
...
def list_files(directory: Path) -> list[Path]:
return list(directory.iterdir())
注意点
文字列への変換
p = Path("folder/file.txt")
# 文字列が必要な場合
str(p) # "folder/file.txt"
# os.fspath でも可
import os
os.fspath(p)
Windowsパス
# Windowsでも / で書ける
p = Path("C:/Users/name/file.txt") # OK
# バックスラッシュはエスケープが必要
p = Path("C:\\Users\\name\\file.txt") # OK
p = Path(r"C:\Users\name\file.txt") # raw文字列でOK
まとめ
| 機能 | メソッド |
|---|---|
| パス結合 | Path("a") / "b" |
| ファイル名 |
.name, .stem, .suffix
|
| 親ディレクトリ | .parent |
| 存在確認 | .exists() |
| 読み書き |
.read_text(), .write_text()
|
| 一覧取得 |
.iterdir(), .glob(), .rglob()
|
| 変換 |
.with_name(), .with_suffix()
|
from pathlib import Path
# これだけで始められる
p = Path(".")
for f in p.rglob("*.py"):
print(f)