Python pathlib
Object-oriented filesystem paths with pathlib.Path.
Path Construction
Path() creates platform-aware paths — use / operator to join, never string concatenation
from pathlib import Path
# Construction
p = Path("/etc/ssl/certs")
config = Path.home() / ".config" / "domus" / "settings.json"
cwd = Path.cwd()
# Joining with / operator -- replaces os.path.join
cert_path = Path("/etc/ssl/certs") / "domus-ca.pem"
# From string variable
raw = "/home/evan/atelier/_bibliotheca"
p = Path(raw)
# Resolve symlinks and make absolute
p = Path("../relative").resolve()
Path Components
Decompose paths into parts — no string splitting needed
from pathlib import Path
p = Path("/etc/ssl/certs/domus-ca.pem")
p.name # "domus-ca.pem" -- filename with extension
p.stem # "domus-ca" -- filename without extension
p.suffix # ".pem" -- extension including dot
p.suffixes # [".pem"] -- all extensions (useful for .tar.gz)
p.parent # Path("/etc/ssl/certs")
p.parents[0] # Path("/etc/ssl/certs")
p.parents[1] # Path("/etc/ssl")
p.parts # ("/", "etc", "ssl", "certs", "domus-ca.pem")
# Change extension
p.with_suffix(".crt") # Path("/etc/ssl/certs/domus-ca.crt")
p.with_name("other.pem") # Path("/etc/ssl/certs/other.pem")
p.with_stem("new-ca") # Path("/etc/ssl/certs/new-ca.pem") -- Python 3.9+
Existence and Type Checks
Check before operating — avoid TOCTOU by catching exceptions in critical paths
from pathlib import Path
p = Path("/etc/hosts")
p.exists() # True if path exists (file, dir, symlink target)
p.is_file() # True if regular file
p.is_dir() # True if directory
p.is_symlink() # True if symlink (even if target missing)
p.is_absolute() # True if path starts from root
# Stat info
p.stat().st_size # file size in bytes
p.stat().st_mtime # modification time (epoch float)
p.owner() # file owner name (Unix only)
Reading and Writing Files
read_text/write_text for small files — no open/close boilerplate
from pathlib import Path
config = Path("config.json")
# Read entire file as string
content = config.read_text(encoding="utf-8")
# Write string to file -- creates or overwrites
config.write_text('{"hostname": "switch-01"}', encoding="utf-8")
# Binary read/write
cert_bytes = Path("/etc/ssl/certs/ca.pem").read_bytes()
Path("copy.pem").write_bytes(cert_bytes)
# For large files, still use open() for streaming
with config.open("r", encoding="utf-8") as f:
for line in f:
process(line)
Directory Operations
mkdir, iterdir, and rmdir — manage directory trees
from pathlib import Path
output = Path("output/reports/2026")
# Create directory tree -- parents=True creates intermediate dirs, exist_ok avoids error
output.mkdir(parents=True, exist_ok=True)
# List directory contents -- returns generator of Path objects
for item in Path("/etc/ssl").iterdir():
print(f"{'DIR ' if item.is_dir() else 'FILE'} {item.name}")
# Remove empty directory
Path("output/empty").rmdir()
# Remove file
Path("output/temp.txt").unlink(missing_ok=True) # missing_ok avoids FileNotFoundError
Glob and Recursive Glob
glob for pattern matching — rglob recurses into subdirectories
from pathlib import Path
docs = Path("docs/modules/ROOT")
# Glob in current directory only
for adoc in docs.glob("*.adoc"):
print(adoc.name)
# Recursive glob -- ** matches any depth
for adoc in docs.rglob("*.adoc"):
print(adoc.relative_to(docs))
# Multiple patterns -- combine with itertools or generator
from itertools import chain
configs = chain(
Path("/etc").rglob("*.conf"),
Path("/etc").rglob("*.cfg"),
)
# Collect into sorted list
all_python = sorted(Path("src").rglob("*.py"))
Common Patterns
Real-world pathlib usage — config dirs, temp files, path manipulation
from pathlib import Path
import json
# XDG-style config directory
config_dir = Path.home() / ".config" / "domus-api"
config_dir.mkdir(parents=True, exist_ok=True)
config_file = config_dir / "settings.json"
# Load or create config
if config_file.exists():
settings = json.loads(config_file.read_text())
else:
settings = {"debug": False, "port": 8000}
config_file.write_text(json.dumps(settings, indent=2))
# Find all test files relative to project root
project_root = Path(__file__).resolve().parent.parent
test_files = sorted(project_root.rglob("test_*.py"))
for tf in test_files:
print(tf.relative_to(project_root))
# Atomic write pattern -- write to temp, then rename (prevents corruption)
import tempfile
tmp = Path(tempfile.mktemp(dir=config_file.parent, suffix=".tmp"))
tmp.write_text(json.dumps(settings, indent=2))
tmp.rename(config_file)