Paths and files
Compose paths, manage resources, and update files safely with pathlib.
pathlib.Path represents a filesystem path, not necessarily an existing file. It avoids manual
separator handling and composes naturally across Windows and POSIX systems.
Build and inspect paths
from pathlib import Path
root = Path.home() / ".example"
config = root / "settings.toml"
print(config.name) # settings.toml
print(config.stem) # settings
print(config.suffix) # .toml
print(config.parent)Path.resolve() makes an absolute path and resolves symlinks. That may touch the filesystem and
change security semantics; do not use it merely to display a path. Path.expanduser() expands
~, while environment variables require explicit handling.
Read and write text
Always state the encoding for durable text formats:
from pathlib import Path
path = Path("notes.txt")
path.write_text("naïve\n", encoding="utf-8")
text = path.read_text(encoding="utf-8")For large files, stream rather than loading everything:
from pathlib import Path
def nonempty_lines(path: Path):
with path.open("r", encoding="utf-8", newline="") as handle:
for line in handle:
stripped = line.strip()
if stripped:
yield strippedA context manager closes the file even if parsing fails. newline="" preserves universal-newline
information where formats such as CSV need it.
Binary data and buffering
Use "rb"/"wb" or read_bytes()/write_bytes() for bytes. Streaming is essential for large
payloads:
from pathlib import Path
def copy_chunks(source: Path, destination: Path) -> None:
with source.open("rb") as reader, destination.open("wb") as writer:
while chunk := reader.read(1024 * 1024):
writer.write(chunk)For ordinary copies that should preserve metadata, prefer shutil.copy2().
Directory traversal
from pathlib import Path
for path in Path("data").glob("**/*.json"):
if path.is_file():
print(path)glob() is convenient; os.scandir() can be faster for carefully tuned traversal. Directory
contents can change at any time. A successful exists() check does not guarantee the next
operation will succeed, so perform the operation and handle its exception.
Creating and deleting
cache = Path("var/cache")
cache.mkdir(parents=True, exist_ok=True)
temporary = cache / "stale.tmp"
temporary.unlink(missing_ok=True)Path.rmdir() removes only empty directories. Use shutil.rmtree() deliberately for recursive
deletion. Never derive a deletion target from unvalidated input.
Atomic replacement
Writing directly to a configuration file can leave partial content after a crash. Write a temporary file in the same directory, flush it, and replace the target:
import os
from pathlib import Path
def replace_text(path: Path, content: str) -> None:
temporary = path.with_suffix(path.suffix + ".tmp")
with temporary.open("w", encoding="utf-8", newline="\n") as handle:
handle.write(content)
handle.flush()
os.fsync(handle.fileno())
os.replace(temporary, path)Replacement is atomic when source and destination are on the same filesystem. This does not by itself coordinate multiple writers; use an application-level lock or single-writer design.
Safe path containment
Joining a user value can escape a root with .. or an absolute path. Resolve both paths and check
containment before access:
from pathlib import Path
def contained_path(root: Path, user_value: str) -> Path:
resolved_root = root.resolve()
candidate = (resolved_root / user_value).resolve()
if not candidate.is_relative_to(resolved_root):
raise ValueError("path escapes the allowed root")
return candidateSymlink races require stronger operating-system-specific techniques in hostile environments.
Common mistakes and practice
- Omitting encodings or assuming path case rules are identical across platforms.
- Using
Pathtruthiness to test existence (Pathobjects are always truthy). - Checking then acting instead of handling
FileNotFoundError,FileExistsError, orPermissionError. - Confusing a filename suffix with trustworthy content validation.
Practice: build a streaming merger for UTF-8 .log files. Skip symlinks, write through a
temporary file, preserve deterministic filename order, and leave the old output intact if any
input cannot be decoded.
LEARNING RECORD
Finish this lesson
Mark it complete when you can explain the main decision without looking.
KNOWLEDGE CHECK