Path Objects and Composition
Path Objects and Composition
pathlib.Path models paths as objects that join with / and decompose cleanly:
from pathlib import Path
p = Path("data") / "raw" / "sales.csv"
p.parts # ('data', 'raw', 'sales.csv')
p.name # 'sales.csv'
p.stem # 'sales'
p.suffix # '.csv'
p.parent # data/raw
list(p.parents) # ancestors, nearest first
Cross-platform joining is automatic: the same code yields data/raw/sales.csv on POSIX and backslashes on Windows. Conversions are explicit — str(path) for APIs, os.fspath(path) where a protocol is expected. Note p.absolute() prepends the cwd without touching the filesystem, while p.resolve() also normalizes .. and follows symlinks (non-strict by default since 3.6).
Reading, Writing, and Walking
Reading, Writing, and Walking
from pathlib import Path
cfg = Path("config.json")
cfg.write_text('{"mode": "prod"}', encoding="utf-8") # always pass encoding
data = cfg.read_text(encoding="utf-8")
Path("out/logs").mkdir(parents=True, exist_ok=True) # idempotent mkdir -p
for csv in Path("data").glob("**/*.csv"): # rglob("**/*.csv") is identical
print(csv, csv.stat().st_size)
if cfg.exists() and cfg.is_file():
cfg.replace("config.bak") # atomic overwrite/rename via os.replace
Directory walking replaces os.walk for most cases: sorted(d.iterdir()), filtering with is_dir()/suffix checks. Path.open() returns a normal file object, so context managers (with p.open() as f:) still manage closure. Never rely on default encodings — locale differences corrupt UTF-8 data on Windows.
Pitfalls and Interview Angles
Pitfalls and Interview Angles
Relative paths are promises, not facts. Path("in.csv") re-resolves against the current working directory every time it is used — a script run from another directory breaks. Anchor with Path(__file__).resolve().parent for file-relative resources.
pathlib rejects bytes paths. Byte-oriented code needs os functions; Path(os.fsdecode(raw)) bridges when necessary.
Glob surprises: patterns match names, not content; glob does not follow .. out of the tree in newer Pythons, and hidden files require glob(".*") explicitly.
Interview angle: contrast os.path.join (string-based, null-usable) with pathlib (object-oriented, richer API), and explain why resolve() is the safe way to canonicalize before comparing two paths.
Quiz
1. What does Path("a/b.txt").with_suffix(".md") return?
2. Why anchor file lookups with Path(__file__).resolve().parent?
3. Difference between absolute() and resolve()?
Flashcards
Question
Path composition
Click to reveal answer
Answer
Path("a") / "b" joins; .name/.stem/.suffix/.parent decompose; separators are platform-native.
Question
mkdir(parents=True, exist_ok=True)
Click to reveal answer
Answer
Idempotent recursive creation — safe to run repeatedly.
Question
Glob vs rglob
Click to reveal answer
Answer
glob("*.py") is one level; glob("**/*.py")/rglob("*.py") recurses the whole subtree.
Question
Encoding rule
Click to reveal answer
Answer
Always pass encoding="utf-8" to read_text/write_text; defaults follow the locale.
Revision Notes
Key Takeaways
- 1. Model paths as Path objects: / composition, structured decomposition, portable separators
- 2. File IO with explicit encoding; directories with parents+exist_ok; discovery with glob/rglob
- 3. Relative paths track the cwd — anchor to __file__ when location matters
Interview Tips
- • Show the pathlib replacement for a messy os.path snippet and note the bytes-path caveat
- • Explain resolve() versus absolute() when asked how you compare or canonicalize paths
Cheat Sheet
Path("a")/"b"; name/stem/suffix/parent; read_text/write_text(encoding="utf-8"); mkdir(parents=True, exist_ok=True); glob("**/*.py"); resolve() vs absolute().
Continue Learning
Before You Start
- No prerequisites listed.