Aller au contenu principal

07.3 - Context Managers & pathlib

Theory 20 min Intermediate

The with Statement

The with statement guarantees that cleanup code runs even if an exception occurs:

# Without with — resource leak if exception!
f = open("data.txt")
data = f.read()
f.close() # might not run!

# With with — always clean
with open("data.txt") as f:
data = f.read()
# f.close() is called automatically

The with statement calls __enter__() on entry and __exit__() on exit.


Custom Context Manager with contextlib

The easiest way to write a context manager:

from contextlib import contextmanager

@contextmanager
def timer(label=""):
"""Context manager that times a block of code."""
import time
start = time.perf_counter()
try:
yield # execution enters the with block here
finally:
elapsed = time.perf_counter() - start
print(f"{label}: {elapsed*1000:.2f}ms")

with timer("Data processing"):
data = [x**2 for x in range(1_000_000)]
# Data processing: 45.23ms
@contextmanager
def managed_file(path, mode="r", **kwargs):
"""Context manager for file operations with error handling."""
f = None
try:
f = open(path, mode, **kwargs)
yield f
except FileNotFoundError:
print(f"File not found: {path}")
yield None
finally:
if f:
f.close()

contextlib.suppress

from contextlib import suppress

# Instead of try/except for FileNotFoundError
with suppress(FileNotFoundError):
import os
os.remove("temp_file.txt") # no error if file doesn't exist

Multiple Context Managers

# Old style
with open("input.txt") as fin:
with open("output.txt", "w") as fout:
fout.write(fin.read())

# Modern style (parentheses)
with (
open("input.txt") as fin,
open("output.txt", "w") as fout
):
fout.write(fin.read())

pathlib Deep Dive

from pathlib import Path

# Basic operations
p = Path("/home/alice/documents/report.pdf")
p.name # "report.pdf"
p.stem # "report"
p.suffix # ".pdf"
p.suffixes # [".pdf"]
p.parent # Path("/home/alice/documents")
p.parents[1] # Path("/home/alice")

# Build paths with /
config = Path.home() / ".config" / "myapp" / "settings.json"

# Check existence
p.exists()
p.is_file()
p.is_dir()
p.is_symlink()

# File info
p.stat().st_size # file size in bytes
p.stat().st_mtime # modification time

# Read/Write
text = p.read_text(encoding="utf-8")
p.write_text("content", encoding="utf-8")
raw = p.read_bytes()
p.write_bytes(raw)

# Directory operations
Path("new_dir").mkdir(exist_ok=True)
Path("nested/dirs").mkdir(parents=True, exist_ok=True)

# Glob patterns
list(Path(".").glob("*.py")) # Python files here
list(Path(".").rglob("*.py")) # Python files recursively
list(Path(".").glob("**/*.json")) # JSON files recursively

# Rename and delete
p.rename("new_name.pdf")
p.unlink() # delete file
p.rmdir() # delete empty directory

Key Vocabulary

TermDefinition
Context managerObject implementing __enter__ and __exit__
@contextmanagerDecorator turning a generator into a context manager
contextlib.suppressContext manager that silences specified exceptions
yieldPause point in a @contextmanager function
pathlib.PathObject-oriented path representation
glob()Pattern matching to find files (e.g., *.py)
rglob()Recursive glob — searches subdirectories too

Summary

  • The with statement calls __enter__ and always calls __exit__, even on exceptions
  • @contextmanager with yield is the simplest way to write context managers
  • contextlib.suppress(ExcType) silently ignores specific exceptions
  • Open multiple context managers in one with block using parentheses (Python 3.10+)
  • pathlib.Path replaces os.path with a clean OOP interface
  • Use Path.glob() and Path.rglob() for file discovery with patterns