Pathlib Patterns
Deep dive · part of Python File Handling
pathlib.Path is the modern, object-oriented replacement for os.path. Use it for joining, globbing, reading, writing, stat-ing and resolving symlinks - all with a portable API.
pathlib.Path replaces os.path string juggling with objects that support / joining, globbing, reading, writing, and stat. Code reads left-to-right: base / 'data' / 'out.csv' instead of os.path.join(base, 'data', 'out.csv').
Paths are not always resolved—distinguish relative paths from absolute resolve() when symlinks matter for security (prevent path traversal).
Production code combines this topic with logging, tests, and clear module boundaries so refactors stay safe when requirements grow.
Path('a') / 'b' creates child paths portably on Windows and POSIX.
read_text/write_text and read_bytes/write_bytes include encoding parameters.
rglob('**/*.py') walks subtrees; glob is non-recursive by default.
exists(), is_file(), is_dir(), suffix, stem, parent properties replace os.path checks.
mkdir(parents=True, exist_ok=True) creates trees safely.
open() delegates to built-in open with Path-compatible API.
For scripts, Path(__file__).resolve().parent anchors resources relative to the module file, not cwd—critical when users launch from another directory. home(), cwd(), and tempdir patterns integrate with shutil for copies.
Security: validate user-supplied filenames with .name only and reject '..'; combine with jail directory resolve().
CI jobs should assert Path.cwd() at start so artifacts never write to wrong workspace when runners change directory.
Anchor resource paths with Path(__file__).resolve().parent so cwd changes do not break assets.
Read the parent tutorial on pythondeck.com for runnable snippets, then reproduce them locally in a virtual environment with pinned dependency versions matching your deployment target.
When pairing with teammates, agree on one idiomatic pattern per concern—mixed styles in one repo slow reviews and invite subtle integration bugs during merges.
Mixing str paths and Path without wrapping Path(s) inconsistently.
Forgetting encoding='utf-8' on text read/write cross-platform.
Using resolve() on every path in hot loops when unnecessary.
Assuming forward slashes in string literals instead of / operator.
Standardize on Path in new code; convert legacy str at boundaries.
Use context managers or one-shot read_text for small config files.
Log resolved paths in deployment errors for support.
Pair pathlib with atomic writes for config updates.
Re-read the examples below with these ideas in mind; change variable names and inputs to match your own project.
The program below demonstrates join + read/write. Read the comments on each line, run the code, then change names or values to see how the output shifts.
# Example: Join + read/write
# Run in the REPL or save as a .py file and execute with python.
from pathlib import Path
base = Path("data")
base.mkdir(exist_ok=True)
(base / "hello.txt").write_text("hi\n", encoding="utf-8")
print((base / "hello.txt").read_text(encoding="utf-8"))
This sample walks through glob in a small, runnable script. Paste it into the REPL or save it as a .py file before you continue to the next block.
# Example: Glob
# Run in the REPL or save as a .py file and execute with python.
from pathlib import Path
for p in Path(".").rglob("*.py"):
print(p, p.stat().st_size, "bytes")
Here is a hands-on illustration of safe walk. Follow the inline comments first; only then execute the snippet and compare the result with what you expected.
# Example: Safe walk
# Run in the REPL or save as a .py file and execute with python.
from pathlib import Path
for p in Path(".").iterdir():
if p.is_dir():
print("dir :", p.name)
elif p.suffix == ".md":
print("md :", p.name)
The program below demonstrates glob scripts. Read the comments on each line, run the code, then change names or values to see how the output shifts.
# Path.rglob recursively matches patterns
from pathlib import Path # OO paths
root = Path(".") # current directory
for py in root.rglob("*.py"): # all Python files below
if py.name.startswith("_"): # skip private modules
continue # next
print(py, py.stat().st_size) # path + bytes
sample = Path("data") # ensure folder
sample.mkdir(exist_ok=True) # create
print(sample.resolve()) # absolute path
This sample walks through read write text in a small, runnable script. Paste it into the REPL or save it as a .py file before you continue to the next block.
# read_text/write_text handle encoding explicitly
from pathlib import Path # pathlib
p = Path("data/note.txt") # file path
p.parent.mkdir(parents=True, exist_ok=True) # parents
p.write_text("hello\n", encoding="utf-8") # write unicode
print(p.read_text(encoding="utf-8")) # read back
print(p.exists(), p.is_file()) # metadata booleans
print(p.suffix, p.stem) # .txt and note
Related deep dives on Python File Handling: