Skip to content

Python Pathlib: モダンなファイルパス操作ガイド

Updated on

もしあなたがos.path.join(os.path.dirname(os.path.abspath(__file__)), 'data', 'output')のようなPythonコードを書いたことがあるなら、すでに問題を知っています。os.pathによる文字列ベースのファイルパス操作は冗長で、読みにくく、エラーが発生しやすいものです。文字列を連結してセパレータを忘れたり、/をハードコーディングしてWindowsでスクリプトが壊れたり、5つのos.path呼び出しを連鎖させてファイルのstem名を取得した結果、3ヶ月後には誰もコードが読めなくなったりします。

これらはエッジケースではありません。すべてのデータサイエンスパイプライン、すべてのWebアプリケーション、すべての自動化スクリプトはファイルシステムに触れます。Macでは動作するパスが、同僚のWindowsラップトップでは失敗します。一時的なパス変数が技術的負債のようにコードに蓄積していきます。そしてos.path.join(os.path.dirname(...))呼び出しをネストするほど、本番環境でのみ表面化する微妙なバグを混入させる可能性が高まります。

Pythonのpathlibモジュールはこれを解決します。Python 3.4で導入され、Python 3.6以降で完全に成熟したpathlibは、文字列ベースのパス操作を適切なPathオブジェクトに置き換えます。パスは/演算子で結合されます。.name.suffix.stemなどのファイル属性はプロパティであり、関数呼び出しではありません。ファイルの読み書きは1行で完了します。そしてすべてがオペレーティングシステム間で同一に動作します。本ガイドでは、基本的なパス構築からデータサイエンスワークフロー向けの高度なパターンまで、pathlibのすべての必須機能を解説します。

なぜpathlibをos.pathより選ぶのか

pathlib以前、Python開発者はパス操作にos.pathを、ファイルシステム操作にosを頼っていました。そのアプローチは機能しますが、パスを単なる文字列として扱います。これにより3つの恒久的な問題が生じます:

  1. 可読性が急速に低下する。 os.path.splitext(os.path.basename(filepath))[0]Path(filepath).stemを比較してください。両方とも拡張子を除いたファイル名を抽出しますが、一方は自己文書化されており、もう一方は精神的に解析する必要があります。

  2. クロスプラットフォームのバグ。 セパレータとして/をハードコーディングしたり、文字列連結を使用すると、LinuxスクリプトがWindowsで静かに壊れます。os.path.joinは助けになりますが、一度でも使用することを忘れると潜在的なバグが生じます。

  3. 散在する機能。 パスを操作するには、分解のためにos.path、ディレクトリ作成のためにos、パターンマッチングのためにglob、ファイルI/Oのためにopen()が必要です。pathlibはこれらすべてを単一のPathオブジェクトに統合します。

以下は同じタスク(データディレクトリ内のすべての.csvファイルを見つけ、最初の1つを読み込む)を両方のスタイルで実装した例です:

# os.path approach
import os
import glob
 
data_dir = os.path.join(os.path.expanduser('~'), 'projects', 'data')
csv_files = glob.glob(os.path.join(data_dir, '**', '*.csv'), recursive=True)
if csv_files:
    with open(csv_files[0], 'r') as f:
        content = f.read()
# pathlib approach
from pathlib import Path
 
data_dir = Path.home() / 'projects' / 'data'
csv_files = list(data_dir.rglob('*.csv'))
if csv_files:
    content = csv_files[0].read_text()

pathlib版はより短く、読みやすく、まったく同じことを行います。Path以外のインポートは不要です。文字列連結はありません。別々のopen()呼び出しもありません。

Pathオブジェクトの作成

すべてのpathlib操作はPathオブジェクトの作成から始まります。Pathクラスは自動的にLinux/macOSではPosixPathを、WindowsではWindowsPathを返します。

from pathlib import Path
 
# From a string
p = Path('/home/user/documents/report.csv')
 
# From multiple segments (joined automatically)
p = Path('home', 'user', 'documents', 'report.csv')
 
# Current working directory
cwd = Path.cwd()
print(cwd)  # e.g., /home/user/projects/myapp
 
# User home directory
home = Path.home()
print(home)  # e.g., /home/user
 
# Relative path
p = Path('data/output/results.csv')
 
# From an existing path
base = Path('/home/user')
full = Path(base, 'documents', 'file.txt')
print(full)  # /home/user/documents/file.txt

引数なしのPath()Path('.')を返し、これは現在のディレクトリへの相対パスです。絶対パスの現在のディレクトリが必要な場合はPath.cwd()を使用してください。

/演算子によるパスの結合

pathlibの最も特徴的な機能は、オーバーロードされた/演算子です。os.path.join()の代わりに、パスセグメントを/で連結します:

from pathlib import Path
 
# Build paths naturally
project = Path.home() / 'projects' / 'analysis'
data_file = project / 'data' / 'sales_2026.csv'
print(data_file)  # /home/user/projects/analysis/data/sales_2026.csv
 
# Mix Path objects and strings
base = Path('/var/log')
app_log = base / 'myapp' / 'error.log'
print(app_log)  # /var/log/myapp/error.log
 
# Combine with variables
filename = 'report.pdf'
output = Path('output') / filename
print(output)  # output/report.pdf

/演算子はセパレータを自動的に処理します。Windowsでは、Path('C:/Users') / 'data'C:\Users\dataを生成します。もう/\について考える必要はありません。

joinpath()を使用しても同じ結果が得られます:

from pathlib import Path
 
# Equivalent to Path('data') / 'raw' / 'file.csv'
p = Path('data').joinpath('raw', 'file.csv')
print(p)  # data/raw/file.csv

パスコンポーネント

すべてのPathオブジェクトは、そのコンポーネントをプロパティとして公開します。関数呼び出しも、文字列の分割も不要です。

from pathlib import Path
 
p = Path('/home/user/projects/analysis/data/sales_report.final.csv')
 
print(p.name)       # sales_report.final.csv  (filename with extension)
print(p.stem)       # sales_report.final      (filename without last extension)
print(p.suffix)     # .csv                    (last extension)
print(p.suffixes)   # ['.final', '.csv']      (all extensions)
print(p.parent)     # /home/user/projects/analysis/data
print(p.anchor)     # /                       (root on Unix, C:\ on Windows)
print(p.parts)      # ('/', 'home', 'user', 'projects', 'analysis', 'data', 'sales_report.final.csv')

親ディレクトリのナビゲート

.parentプロパティは直近の親ディレクトリを返します。これを連鎖させて上位に移動します:

from pathlib import Path
 
p = Path('/home/user/projects/analysis/data/output.csv')
 
print(p.parent)            # /home/user/projects/analysis/data
print(p.parent.parent)     # /home/user/projects/analysis
print(p.parent.parent.parent)  # /home/user/projects
 
# .parents gives indexed access to all ancestors
print(p.parents[0])  # /home/user/projects/analysis/data
print(p.parents[1])  # /home/user/projects/analysis
print(p.parents[2])  # /home/user/projects
print(p.parents[3])  # /home/user

パスコンポーネントの変更

.with_name().with_stem().with_suffix()を使用して、変更されたコンポーネントを持つ新しいパスを作成します:

from pathlib import Path
 
p = Path('/data/reports/sales_q1.csv')
 
# Change the filename entirely
print(p.with_name('revenue_q1.csv'))    # /data/reports/revenue_q1.csv
 
# Change only the stem (Python 3.9+)
print(p.with_stem('sales_q2'))          # /data/reports/sales_q2.csv
 
# Change only the extension
print(p.with_suffix('.parquet'))        # /data/reports/sales_q1.parquet
 
# Remove the extension
print(p.with_suffix(''))                # /data/reports/sales_q1
 
# Add an extension
backup = p.with_suffix(p.suffix + '.bak')
print(backup)                           # /data/reports/sales_q1.csv.bak

これらのメソッドは新しいPathオブジェクトを返します。ディスク上のファイル名は変更しません。

ファイルI/O:読み書き

pathlibは、単純なファイル操作におけるopen()/withのボイラープレートを排除します:

from pathlib import Path
 
file_path = Path('example.txt')
 
# Write text to a file (creates if not exists, overwrites if exists)
file_path.write_text('Hello, pathlib!\nSecond line.')
 
# Read text from a file
content = file_path.read_text()
print(content)
# Hello, pathlib!
# Second line.
 
# Write bytes
binary_path = Path('data.bin')
binary_path.write_bytes(b'\x00\x01\x02\x03')
 
# Read bytes
raw = binary_path.read_bytes()
print(raw)  # b'\x00\x01\x02\x03'

非ASCIIテキストを扱う場合は、エンコーディングを明示的に指定してください:

from pathlib import Path
 
# Write UTF-8 text
Path('greeting.txt').write_text('こんにちは世界', encoding='utf-8')
 
# Read with encoding
text = Path('greeting.txt').read_text(encoding='utf-8')
print(text)  # こんにちは世界

大きなファイルやストリーミング操作の場合、ビルトインopen()と同様にファイルハンドルを返す.open()を使用します:

from pathlib import Path
 
log_file = Path('application.log')
 
# Write line by line
with log_file.open('w') as f:
    for i in range(1000):
        f.write(f'Event {i}: processed\n')
 
# Read line by line (memory-efficient for large files)
with log_file.open('r') as f:
    for line in f:
        if 'error' in line.lower():
            print(line.strip())

ディレクトリ操作

ディレクトリの作成

from pathlib import Path
 
# Create a single directory
Path('output').mkdir()
 
# Create with parents (like os.makedirs)
Path('data/raw/2026/february').mkdir(parents=True, exist_ok=True)
 
# parents=True creates all missing parent directories
# exist_ok=True prevents error if directory already exists

よくある間違いはparents=Trueを忘れることです。これがないと、親ディレクトリが存在しない場合mkdir()FileNotFoundErrorを発生させます。ネストしたディレクトリを作成する際は常にparents=Trueを使用し、操作を冪等にするためにexist_ok=Trueを使用してください。

ディレクトリ内容の一覧表示

from pathlib import Path
 
project = Path('.')
 
# List all entries (files and directories)
for entry in project.iterdir():
    print(entry.name, '(dir)' if entry.is_dir() else '(file)')
 
# Filter to files only
files = [f for f in project.iterdir() if f.is_file()]
print(f"Found {len(files)} files")
 
# Filter to directories only
dirs = [d for d in project.iterdir() if d.is_dir()]
print(f"Found {len(dirs)} directories")
 
# Sort by name
for entry in sorted(project.iterdir()):
    print(entry.name)

ディレクトリとファイルの削除

from pathlib import Path
 
# Remove a file
Path('temp_output.csv').unlink()
 
# Remove a file only if it exists (Python 3.8+)
Path('temp_output.csv').unlink(missing_ok=True)
 
# Remove an empty directory
Path('empty_dir').rmdir()

rmdir()は空のディレクトリのみを削除します。空でないディレクトリにはshutil.rmtree()を使用します:

from pathlib import Path
import shutil
 
target = Path('data/old_output')
if target.exists():
    shutil.rmtree(target)

Globパターン:ファイルの検索

pathlibには組み込みのglobサポートがあります。globモジュールを別途インポートする必要はありません。

基本のGlob

from pathlib import Path
 
project = Path('/home/user/project')
 
# Find all Python files in a directory
for py_file in project.glob('*.py'):
    print(py_file.name)
 
# Find all CSV files
csv_files = list(project.glob('*.csv'))
print(f"Found {len(csv_files)} CSV files")
 
# Find files matching a pattern
reports = list(project.glob('report_*.xlsx'))

rglobによる再帰的Glob

rglob()はすべてのサブディレクトリを再帰的に検索します。これはglob('**/*.pattern')と同等ですが、より便利です:

from pathlib import Path
 
project = Path('/home/user/project')
 
# Find all Python files in all subdirectories
all_py = list(project.rglob('*.py'))
print(f"Found {len(all_py)} Python files across all directories")
 
# Find all Jupyter notebooks recursively
notebooks = list(project.rglob('*.ipynb'))
for nb in notebooks:
    print(f"  {nb.relative_to(project)}")
 
# Find all image files
images = list(project.rglob('*.png')) + list(project.rglob('*.jpg'))
 
# Find all files (no filter)
all_files = [f for f in project.rglob('*') if f.is_file()]

高度なGlobパターン

from pathlib import Path
 
data = Path('data')
 
# Single character wildcard
data.glob('file_?.csv')        # file_1.csv, file_a.csv
 
# Character ranges
data.glob('report_202[456].csv')  # report_2024.csv, report_2025.csv, report_2026.csv
 
# Any subdirectory level
data.glob('**/output/*.csv')   # data/raw/output/result.csv, data/processed/output/result.csv
 
# Multiple extensions (combine two globs)
from itertools import chain
all_data = chain(data.rglob('*.csv'), data.rglob('*.parquet'))

パスの確認

pathlibは、パスの状態を確認するための明確なブールメソッドを提供します:

from pathlib import Path
 
p = Path('/home/user/projects/data.csv')
 
# Does the path exist?
print(p.exists())       # True or False
 
# Is it a file?
print(p.is_file())      # True if exists and is a regular file
 
# Is it a directory?
print(p.is_dir())       # True if exists and is a directory
 
# Is it a symbolic link?
print(p.is_symlink())   # True if exists and is a symlink
 
# Is it an absolute path?
print(p.is_absolute())  # True (/home/... starts with root)
print(Path('data.csv').is_absolute())  # False (relative path)

これらのメソッドは、存在しないパスに対して例外を発生させることはありません。単にFalseを返すため、条件分岐で安全に使用できます:

from pathlib import Path
 
config = Path('config.yaml')
if config.is_file():
    settings = config.read_text()
else:
    print("Config file not found, using defaults")

パス操作

パスの解決と正規化

from pathlib import Path
 
# Resolve to absolute path (also resolves symlinks)
p = Path('data/../data/./output.csv')
print(p.resolve())  # /home/user/project/data/output.csv
 
# Get absolute path without resolving symlinks
print(p.absolute())  # /home/user/project/data/../data/./output.csv
 
# Expand user home directory
p = Path('~/Documents/report.csv')
print(p.expanduser())  # /home/user/Documents/report.csv

相対パス

from pathlib import Path
 
full_path = Path('/home/user/projects/analysis/data/output.csv')
base = Path('/home/user/projects')
 
# Get the relative path from base to full_path
relative = full_path.relative_to(base)
print(relative)  # analysis/data/output.csv
 
# This raises ValueError if the path is not relative to the base
try:
    Path('/var/log/app.log').relative_to(base)
except ValueError as e:
    print(e)  # '/var/log/app.log' is not relative to '/home/user/projects'
 
# Python 3.12+: is_relative_to() check
print(full_path.is_relative_to(base))   # True
print(Path('/var/log').is_relative_to(base))  # False

ファイルメタデータとStat

from pathlib import Path
from datetime import datetime
 
p = Path('data.csv')
 
# Get file stats
stat = p.stat()
print(f"Size: {stat.st_size} bytes")
print(f"Modified: {datetime.fromtimestamp(stat.st_mtime)}")
print(f"Created: {datetime.fromtimestamp(stat.st_ctime)}")
 
# Convenience: get size directly (through stat)
size_mb = p.stat().st_size / (1024 * 1024)
print(f"Size: {size_mb:.2f} MB")
 
# Check if two paths point to the same file
p1 = Path('/home/user/data.csv')
p2 = Path.home() / 'data.csv'
print(p1.samefile(p2))  # True (if they resolve to the same file)

ファイルの名前変更と移動

from pathlib import Path
 
# Rename a file (returns the new Path)
old = Path('report_draft.csv')
new = old.rename('report_final.csv')
print(new)  # report_final.csv
 
# Move to a different directory
source = Path('output/temp_results.csv')
dest = source.rename(Path('archive') / source.name)
 
# Replace a file (overwrites if destination exists)
Path('new_data.csv').replace('data.csv')

注意:.rename()はUnixでは宛先ファイルを上書きしますが、Windowsではエラーを発生させる場合があります。確実なクロスプラットフォームの上書き動作には.replace()を使用してください。

os.path vs pathlib:完全な比較

以下は、一般的なos.path操作をpathlibの同等のものにマッピングした参照テーブルです:

操作os.path / ospathlib
パスの結合os.path.join('a', 'b')Path('a') / 'b'
現在のディレクトリos.getcwd()Path.cwd()
ホームディレクトリos.path.expanduser('~')Path.home()
絶対パスos.path.abspath(p)Path(p).resolve()
ファイル名os.path.basename(p)Path(p).name
ディレクトリos.path.dirname(p)Path(p).parent
拡張子os.path.splitext(p)[1]Path(p).suffix
Stem(拡張子を除いた名前)os.path.splitext(os.path.basename(p))[0]Path(p).stem
存在確認os.path.exists(p)Path(p).exists()
ファイルかどうかos.path.isfile(p)Path(p).is_file()
ディレクトリかどうかos.path.isdir(p)Path(p).is_dir()
シンボリックリンクかどうかos.path.islink(p)Path(p).is_symlink()
絶対パスかどうかos.path.isabs(p)Path(p).is_absolute()
ファイルサイズos.path.getsize(p)Path(p).stat().st_size
ディレクトリ一覧os.listdir(p)Path(p).iterdir()
ディレクトリ作成os.makedirs(p, exist_ok=True)Path(p).mkdir(parents=True, exist_ok=True)
ファイル削除os.remove(p)Path(p).unlink()
ディレクトリ削除os.rmdir(p)Path(p).rmdir()
名前変更os.rename(old, new)Path(old).rename(new)
ファイル読み込みopen(p).read()Path(p).read_text()
ファイル書き込みopen(p, 'w').write(text)Path(p).write_text(text)
Globglob.glob('*.py')Path('.').glob('*.py')
再帰的Globglob.glob('**/*.py', recursive=True)Path('.').rglob('*.py')
ユーザーディレクトリ展開os.path.expanduser(p)Path(p).expanduser()
相対パスos.path.relpath(p, base)Path(p).relative_to(base)

一時ファイルの扱い

pathlibはPythonのtempfileモジュールとクリーンに統合されます:

from pathlib import Path
import tempfile
 
# Create a temporary directory as a Path
with tempfile.TemporaryDirectory() as tmp_dir:
    tmp_path = Path(tmp_dir)
 
    # Write temporary files using pathlib
    data_file = tmp_path / 'intermediate_results.csv'
    data_file.write_text('col1,col2\n1,2\n3,4\n')
 
    config_file = tmp_path / 'run_config.json'
    config_file.write_text('{"epochs": 100, "lr": 0.001}')
 
    # List what we created
    for f in tmp_path.iterdir():
        print(f"{f.name}: {f.stat().st_size} bytes")
 
    # Process files...
    print(data_file.read_text())
 
# Directory and all files are automatically deleted here
from pathlib import Path
import tempfile
 
# Create a named temporary file
tmp = tempfile.NamedTemporaryFile(suffix='.csv', delete=False)
tmp_path = Path(tmp.name)
tmp.close()
 
# Use pathlib to write to it
tmp_path.write_text('id,value\n1,100\n2,200\n')
print(f"Temp file at: {tmp_path}")
 
# Clean up when done
tmp_path.unlink()

データサイエンスワークフローにおけるPathlib

データサイエンスプロジェクトでは通常、複数のディレクトリからデータセットを読み込み、結果用の出力フォルダを作成し、実験アーティファクトを管理します。pathlibはこれらのパターンをクリーンで信頼性のあるものにします。

プロジェクトディレクトリの整理

from pathlib import Path
 
def setup_experiment(experiment_name):
    """Create a standard experiment directory structure."""
    base = Path('experiments') / experiment_name
 
    dirs = ['data/raw', 'data/processed', 'models', 'results/figures', 'results/tables', 'logs']
 
    for d in dirs:
        (base / d).mkdir(parents=True, exist_ok=True)
 
    # Create a config file
    config = base / 'config.json'
    if not config.exists():
        config.write_text('{"learning_rate": 0.001, "epochs": 50}')
 
    print(f"Experiment directory ready: {base.resolve()}")
    return base
 
project = setup_experiment('sales_forecast_v2')

複数のデータファイルの読み込み

from pathlib import Path
import pandas as pd
 
data_dir = Path('data/raw')
 
# Read all CSV files into a single DataFrame
dfs = []
for csv_file in sorted(data_dir.glob('*.csv')):
    print(f"Loading {csv_file.name}...")
    df = pd.read_csv(csv_file)
    df['source_file'] = csv_file.stem  # Add source filename
    dfs.append(df)
 
combined = pd.concat(dfs, ignore_index=True)
print(f"Loaded {len(combined)} rows from {len(dfs)} files")
 
# Save to processed directory
output_path = Path('data/processed') / 'combined_sales.parquet'
output_path.parent.mkdir(parents=True, exist_ok=True)
combined.to_parquet(output_path)

pathlibでCSVデータを読み込んだ後、PyGWalker (opens in a new tab)を使用して視覚的に探索できます。これはあらゆるPandas DataFrameを、Tableauのようなドラッグ&ドロップ式のインタラクティブインターフェースに変換し、追加のコードを必要としません。

実験結果の保存

from pathlib import Path
from datetime import datetime
import json
 
def save_results(metrics, experiment_dir):
    """Save experiment metrics with timestamp."""
    results_dir = Path(experiment_dir) / 'results'
    results_dir.mkdir(parents=True, exist_ok=True)
 
    timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
    output_file = results_dir / f'metrics_{timestamp}.json'
 
    output_file.write_text(json.dumps(metrics, indent=2))
    print(f"Results saved to {output_file}")
    return output_file
 
# Usage
metrics = {'accuracy': 0.94, 'f1_score': 0.91, 'loss': 0.187}
save_results(metrics, 'experiments/sales_forecast_v2')

Notebookでのファイルパス管理

Jupyter notebookで作業する際、notebookの作業ディレクトリがプロジェクトルートと異なる場合があるため、パスが壊れることがよくあります。pathlibはこれを簡単に処理できます:

from pathlib import Path
 
# Always resolve to absolute path from the notebook location
NOTEBOOK_DIR = Path.cwd()
PROJECT_ROOT = NOTEBOOK_DIR.parent  # if notebook is in notebooks/
DATA_DIR = PROJECT_ROOT / 'data'
OUTPUT_DIR = PROJECT_ROOT / 'output'
 
# Now all paths are absolute and reliable
train_data = DATA_DIR / 'train.csv'
print(f"Loading: {train_data}")
assert train_data.exists(), f"Missing: {train_data}"

Jupyterで広範に作業し、プロジェクトファイルやデータパスの管理を支援するAI駆動の環境が必要な場合、RunCell (opens in a new tab)はあなたのnotebookにAIエージェントレイヤーを追加します。「dataディレクトリ内のすべてのParquetファイルを見つけて最新のものを読み込む」といったニーズを説明すれば、pathlibコードを生成して実行します。

一般的なパターンとレシピ

安全なファイル書き込み(アトミック置換)

一時ファイルに先に書き込んでからアトミックに置き換えることで、データ破損を防ぎます:

from pathlib import Path
import tempfile
 
def safe_write(target_path, content):
    """Write content to file atomically to prevent corruption."""
    target = Path(target_path)
    target.parent.mkdir(parents=True, exist_ok=True)
 
    # Write to temp file in the same directory
    tmp = tempfile.NamedTemporaryFile(
        mode='w', dir=target.parent, suffix='.tmp', delete=False
    )
    tmp_path = Path(tmp.name)
    try:
        tmp.write(content)
        tmp.close()
        tmp_path.replace(target)  # Atomic on most file systems
    except Exception:
        tmp_path.unlink(missing_ok=True)
        raise
 
safe_write('config/settings.json', '{"debug": true}')

バッチファイル名変更

from pathlib import Path
 
photos_dir = Path('photos')
 
# Rename all .jpeg files to .jpg
for f in photos_dir.glob('*.jpeg'):
    f.rename(f.with_suffix('.jpg'))
 
# Add prefix to all files
for i, f in enumerate(sorted(photos_dir.glob('*.jpg')), start=1):
    new_name = f.parent / f'photo_{i:04d}{f.suffix}'
    f.rename(new_name)

サイズによる重複ファイル検索

from pathlib import Path
from collections import defaultdict
 
def find_potential_duplicates(directory):
    """Find files with identical sizes (potential duplicates)."""
    size_map = defaultdict(list)
 
    for f in Path(directory).rglob('*'):
        if f.is_file():
            size_map[f.stat().st_size].append(f)
 
    # Return only groups with more than one file
    return {size: files for size, files in size_map.items() if len(files) > 1}
 
dupes = find_potential_duplicates('data')
for size, files in dupes.items():
    print(f"\n{size} bytes:")
    for f in files:
        print(f"  {f}")

ファイルツリー可視化の構築

from pathlib import Path
 
def tree(directory, prefix='', max_depth=3, _depth=0):
    """Print a tree structure of a directory."""
    if _depth >= max_depth:
        return
 
    path = Path(directory)
    entries = sorted(path.iterdir(), key=lambda e: (e.is_file(), e.name))
 
    for i, entry in enumerate(entries):
        is_last = (i == len(entries) - 1)
        connector = '└── ' if is_last else '├── '
        print(f'{prefix}{connector}{entry.name}')
 
        if entry.is_dir():
            extension = '    ' if is_last else '│   '
            tree(entry, prefix + extension, max_depth, _depth + 1)
 
tree('my_project', max_depth=3)

Output:

├── data
│   ├── processed
│   │   └── combined.csv
│   └── raw
│       ├── sales_2025.csv
│       └── sales_2026.csv
├── notebooks
│   └── analysis.ipynb
├── output
│   └── figures
└── requirements.txt

よくある間違いとその回避方法

間違い1:文字列とPathオブジェクトの比較

from pathlib import Path
 
p = Path('data/output.csv')
 
# WRONG: Comparing string to Path
if p == 'data/output.csv':  # May work but fragile
    print("Match")
 
# RIGHT: Compare Path to Path, or use str()
if p == Path('data/output.csv'):
    print("Match")
 
# RIGHT: Convert to string if needed
if str(p) == 'data/output.csv':
    print("Match")

間違い2:mkdirでparents=Trueを忘れる

from pathlib import Path
 
# WRONG: Raises FileNotFoundError if 'data' doesn't exist
# Path('data/raw/2026').mkdir()
 
# RIGHT: Create all missing parents
Path('data/raw/2026').mkdir(parents=True, exist_ok=True)

間違い3:/の代わりに文字列連結を使用

from pathlib import Path
 
base = Path('/home/user')
 
# WRONG: String concatenation breaks pathlib
# bad = base + '/data/file.csv'  # TypeError
 
# RIGHT: Use the / operator
good = base / 'data' / 'file.csv'

間違い4:文字列を期待するライブラリにPathを渡す

ほとんどのモダンなライブラリ(Pandas、NumPy、PILなど)はPathオブジェクトをネイティブに受け入れます。しかし、文字列を必要とする古いライブラリに遭遇した場合は、明示的に変換してください:

from pathlib import Path
 
p = Path('data/output.csv')
 
# Most libraries accept Path directly
import pandas as pd
df = pd.read_csv(p)  # Works fine
 
# For older libraries that need strings
import some_legacy_lib
some_legacy_lib.process(str(p))  # Convert with str()
 
# os.fspath() also works (Python 3.6+)
import os
some_legacy_lib.process(os.fspath(p))

間違い5:ハードコードされたパスの使用

from pathlib import Path
 
# WRONG: Hardcoded absolute path
# data_path = Path('/home/alice/project/data/sales.csv')
 
# RIGHT: Build from relative or dynamic components
data_path = Path.cwd() / 'data' / 'sales.csv'
 
# RIGHT: Build from home directory
config_path = Path.home() / '.config' / 'myapp' / 'settings.json'
 
# RIGHT: Build from environment variable
import os
data_root = Path(os.getenv('DATA_DIR', 'data'))
data_path = data_root / 'sales.csv'

よくある質問

Pythonのpathlibとは何ですか?

pathlibは、Python 3.4で導入された標準ライブラリモジュールで、ファイルシステムパスを扱うためのオブジェクト指向クラスを提供します。os.path.join()のような関数を使用してパスを文字列として扱う代わりに、Pathオブジェクトを作成してメソッドや演算子を使用します。クロスプラットフォームのパスの違いを自動的に処理します。

いつos.pathの代わりにpathlibを使用すべきですか?

Python 3.6以降のすべての新しいプロジェクトでpathlibを使用してください。よりクリーンで読みやすいコードを生み出し、パス操作を単一のオブジェクトに統合し、クロスプラットフォームの問題を自動的に処理します。os.pathを使用する唯一の理由は、Python 2をサポートしなければならないレガシーコードを維持する場合、またはos.environのようなpathlibに相当するものがないos関数を使用する場合です。

pathlibはWindowsで動作しますか?

はい。pathlibはWindowsでは自動的にWindowsPathオブジェクトを使用し、Linux/macOSではPosixPathを使用します。/演算子はWindowsではバックスラッシュで区切られたパスを生成します。すべてのプラットフォームで同じコードを書き、pathlibが違いを処理します。

PathオブジェクトをPandasで使用できますか?

はい。Python 3.6とPandas 0.21以降、pd.read_csv()pd.read_excel()df.to_csv()、およびその他のI/O関数にPathオブジェクトを直接渡すことができます。str()変換は不要です。

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

.resolve()は絶対パスを返し、シンボリックリンクや../.コンポーネントも解決します。.absolute()は絶対パスを返しますが、シンボリックリンクを解決したりパスを正規化したりしません。ほとんどの場合、.resolve()が必要です。

Pathオブジェクトと文字列の間で変換するにはどうすればよいですか?

Pathを文字列に変換するにはstr(path)を使用し、文字列からPathを作成するにはPath(string)を使用します。os.fspath(path)を使用して明示的に文字列変換することもできます。ほとんどのモダンなPythonライブラリはPathオブジェクトを直接受け入れるため、変換はめったに必要ありません。

結論

Pythonのpathlibモジュールは、ファイルパス操作のモダンな標準です。/演算子はパス結合を読みやすくします。.name.stem.suffix.parentなどのプロパティは、冗長なos.path関数チェーンを排除します。読み書き、ディレクトリ作成、globのための組み込みメソッドは、以前はosos.pathglobopen()を必要としたものを、単一の一貫したAPIに統合します。

os.pathからpathlibへの移行は簡単です:os.path.join()/に置き換え、os.path.exists().exists()に置き換え、os.makedirs().mkdir(parents=True)に置き換え、glob.glob().glob()または.rglob()に置き換えます。Pandas、NumPy、PIL、PyTorchなど、主要なPythonライブラリはすべてPathオブジェクトをネイティブに受け入れます。新しいプロジェクトでこれを避ける理由はありません。

小さく始めましょう。乱雑なos.pathコードがあるスクリプトを1つ選び、パス操作をpathlibに置き換えます。コードは短く、読みやすく、移植性が高くなります。次のスクリプトでも同じことを行います。

📚