Files & I/O
Reading and writing files is a daily task - logs, config, uploads, and exports. pathlib.Path provides an object-oriented path API, while context managers ensure descriptors close even when errors occur.
Search across all documentation pages
Reading and writing files is a daily task - logs, config, uploads, and exports. pathlib.Path provides an object-oriented path API, while context managers ensure descriptors close even when errors occur.
from pathlib import Path
path = Path("data/config.json")
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text('{"debug": true}', encoding="utf-8")
raw = path.read_bytes()
text = raw.decode("utf-8")When to reach for this:
import json
import tempfile
from pathlib import Path
def atomic_write_json(path: Path, payload: dict) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
with tempfile.NamedTemporaryFile(
mode="w",
encoding="utf-8",
dir=path.parent,
delete=False,
suffix=".tmp",
) as tmp:
json.dump(payload, tmp, indent=2)
tmp_path = Path(tmp.name)
tmp_path.replace(path)
def tail_lines(path: Path, max_lines: int = 50) -> list[str]:
if not path.exists():
return []
lines = path.read_text(encoding="utf-8").splitlines()
return lines[-max_lines:]
def copy_binary(src: Path, dst: Path, chunk_size: int = 1024 * 64) -> None:
dst.parent.mkdir(parents=True, exist_ok=True)
with src.open("rb") as rf, dst.open("wb") as wf:
while chunk := rf.read(chunk_size):
wf.write(chunk)
if __name__ == "__main__":
cfg = Path("data/settings.json")
atomic_write_json(cfg, {"retries": 3, "timeout": 30})
print(tail_lines(Path("data/app.log")))What this demonstrates:
Path.replaceencoding="utf-8"mkdir(parents=True, exist_ok=True) creates nested directories safely"r"/"w" return str; "rb"/"wb" return bytes.with open(...) as f calls f.close() in finally.os/open with Path methods (read_text, glob, iterdir).strict, replace).with.| Task | pathlib |
|---|---|
| Join paths | base / "child" / "file.txt" |
| Exists check | path.exists() |
| Glob | path.glob("**/*.py") |
| Resolve | path.resolve() absolute path |
from contextlib import ExitStack
with ExitStack() as stack:
a = stack.enter_context(path_a.open())
b = stack.enter_context(path_b.open())
# both close on exitencoding="utf-8" always for text."dir" + "/file" breaks on Windows. Fix: Path("dir") / "file".exists() then open - another process may delete. Fix: try/except FileNotFoundError.line + "\n" or print(..., file=fh).| Alternative | Use When | Don't Use When |
|---|---|---|
pathlib | Most file tasks | Need lowest-level os.open flags |
aiofiles | Async FastAPI uploads | Sync CLI script |
tempfile | Atomic writes, scratch | Permanent storage paths |
| Object storage (S3) | Durable shared assets | Local dev config only |
Prefer pathlib for new code - readable operators and high-level read/write helpers.
with path.open("a", encoding="utf-8") as fh: fh.write(line + "\n")
"rb" and "wb" - never decode images as UTF-8 text.
for p in Path("src").rglob("*.py"): print(p)
Write temp in same filesystem directory, then Path.replace - readers see old or new file, never partial.
Catch FileNotFoundError or check path.exists() when optional config is acceptable.
No - read text then json.loads. Or use json.load with an open file handle.
strict for validated pipelines; replace or backslashreplace for lossy log ingestion only.
path.stat().st_size without reading contents.
Yes - context manager protocol runs __exit__ and closes the file even when an error propagates.
with patternsStack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.
Reviewed by Chris St. John·Last updated Jul 16, 2026