pathlib & Modern File Handling
debt(d3/e3/b3/t5)
Closest to 'default linter catches the common case' (d3) — ruff and pylint can flag os.path usage patterns where pathlib would be preferred, though not all stylistic cases are caught.
Closest to 'simple parameterised fix' (e3) — quick_fix is a pattern replacement (os.path.join → Path / operator) applied per-call site; not a single line if used widely, but mechanical.
Closest to 'localised tax' (b3) — applies to filesystem code in web/cli contexts; mixing os.path and Path adds friction but doesn't define system shape.
Closest to 'notable trap' (t5) — misconception that os.path equals pathlib, plus documented gotchas like glob() not being recursive and mkdir raising without exist_ok=True are classic learn-once surprises.
Also Known As
TL;DR
Explanation
pathlib (Python 3.4+) represents paths as Path objects with methods: / operator joins paths, .read_text() / .write_text() / .read_bytes(), .exists() / .is_file() / .is_dir(), .glob() / .rglob() for pattern matching, .stem / .suffix / .parent / .name attributes, .mkdir(parents=True, exist_ok=True). Avoids the fragile string concatenation of os.path.join(). Works correctly on Windows (backslash) and Unix (forward slash). Use Path objects from the start — do not mix Path and string manipulation.
Common Misconception
Why It Matters
Common Mistakes
- Mixing string concatenation with Path objects — always use / operator or Path() constructor.
- Not using exist_ok=True in mkdir — raises if directory exists.
- glob() not being recursive — use rglob('**/*.py') or rglob with a pattern for recursive matching.
- Converting Path to string with str() when passing to legacy APIs — unnecessary in modern Python.
Code Examples
import os
# String-based path manipulation — fragile:
base = '/var/www/app'
config_path = base + '/config' + '/settings.json' # Wrong on Windows
if os.path.exists(config_path):
with open(config_path) as f:
data = f.read()
os.makedirs(os.path.join(base, 'logs'), exist_ok=True)
from pathlib import Path
base = Path('/var/www/app')
config_path = base / 'config' / 'settings.json' # / operator, cross-platform
if config_path.exists():
data = config_path.read_text(encoding='utf-8') # No with-open needed
(base / 'logs').mkdir(parents=True, exist_ok=True)
# Find all Python files recursively:
for py_file in base.rglob('*.py'):
print(py_file.stem, py_file.suffix)