File I/O & Context Managers
15 questions found'with open(path) as f:' guarantees the file is properly closed when the block exits, EVEN IF an exception occurs inside it — manually calling open() and close() risks leaking the file handle if an exception happens between them and close() is never reached.
with open("data.txt") as f:
content = f.read()
print(content)
# file is automatically closed here, even if .read() had raised an exception
Real-world example Reading a configuration or data file safely, guaranteeing the file handle is released even if reading fails partway through.
Common follow-ups: What exception type does open() raise if the specified file doesn't exist?
Exception Handling
read() returns the ENTIRE file's contents as a single string; readline() returns just the NEXT single line (including its trailing newline); readlines() returns a LIST of every line in the file — read() and readlines() both load the whole file into memory at once.
with open("data.txt") as f:
all_content = f.read() # one big string
with open("data.txt") as f:
first_line = f.readline() # just one line
with open("data.txt") as f:
lines = f.readlines() # a list of every line
Real-world example Choosing readline() to process a huge file one line at a time versus read() for a small file you need entirely at once.
Common follow-ups: How does iterating directly over the file object (for line in f) compare to calling readlines()?
Memory Management & Garbage Collection
'r' opens for reading (default, error if the file doesn't exist); 'w' opens for writing, TRUNCATING (erasing) any existing content; 'a' opens for APPENDING to the end without erasing existing content; adding 'b' to any mode (like 'rb') opens the file in BINARY mode, returning bytes instead of decoded text strings.
with open("log.txt", "a") as f:
f.write("New log entry\n") # appends, doesn't erase existing content
with open("image.png", "rb") as f:
data = f.read() # returns bytes, not a decoded string
Real-world example Using 'a' mode to append new entries to a growing log file without ever overwriting previous entries.
Common follow-ups: What happens if you open a file with 'w' mode and the file already contains important data?
Data Types & Structures
Define a class with __enter__ (running setup logic and returning the value bound by 'as') and __exit__ (running cleanup logic, receiving any exception info that occurred inside the 'with' block) — the 'with' statement automatically calls __enter__ on entry and __exit__ on exit, regardless of how the block ends.
class Timer:
def __enter__(self):
self.start = time.time()
return self
def __exit__(self, exc_type, exc_value, tb):
elapsed = time.time() - self.start
print(f"Took {elapsed:.2f}s")
with Timer():
do_expensive_work()
Real-world example Building a custom resource manager (like a database connection or a performance timer) with guaranteed setup/teardown behavior.
Common follow-ups: What should __exit__ return if you want an exception raised inside the 'with' block to still propagate normally?
Exception Handling
Decorate a generator function with @contextmanager; code BEFORE the single 'yield' runs as __enter__ (the yielded value becomes the 'as' target), and code AFTER 'yield' (typically wrapped in try/finally) runs as __exit__ — a much more concise alternative to writing a full class with two dunder methods.
from contextlib import contextmanager
import time
@contextmanager
def timer():
start = time.time()
try:
yield
finally:
print(f"Took {time.time() - start:.2f}s")
with timer():
do_expensive_work()
Real-world example Writing a lightweight, function-based context manager without the boilerplate of a full class-based __enter__/__exit__ implementation.
Common follow-ups: Why must the yield inside a @contextmanager-decorated generator typically be wrapped in a try/finally?
Decorators
pathlib.Path represents a filesystem path as an OBJECT with useful methods and operator overloading (like '/' for joining paths), providing a more readable, cross-platform alternative to os.path's string-concatenation-based functions.
from pathlib import Path
data_dir = Path("data")
file_path = data_dir / "input.txt" # '/' operator joins paths cleanly
print(file_path.exists()) # True/False
print(file_path.suffix) # '.txt'
print(file_path.read_text()) # reads the entire file as a string, no 'with' needed
Real-world example Building cross-platform file path manipulation code that works consistently on both Windows and Unix-like systems.
Common follow-ups: How does Path.read_text() internally handle opening and closing the file, compared to a manual 'with open()' block?
Modules & Packaging
Separate multiple context managers with commas in one 'with' statement (`with open(a) as f1, open(b) as f2:`) — functionally equivalent to nesting them, but more concise; all context managers are entered left-to-right and exited in REVERSE order, exactly like nested blocks would be.
with open("input.txt") as infile, open("output.txt", "w") as outfile:
for line in infile:
outfile.write(line.upper())
# both files properly closed, even nested exceptions are handled correctly
Real-world example Reading from one file and writing to another simultaneously, with both files guaranteed to be properly closed.
Common follow-ups: In what order are the context managers' __exit__ methods called if an exception occurs partway through the block?
Exception Handling
Save the ORIGINAL working directory in __enter__ before changing to the new one, then restore it in __exit__ (or the equivalent finally block in a @contextmanager generator) — ensuring the change is always reverted even if an exception occurs while the directory is temporarily changed.
import os
from contextlib import contextmanager
@contextmanager
def temporary_directory(path):
original = os.getcwd()
os.chdir(path)
try:
yield
finally:
os.chdir(original) # always restored, even on exception
with temporary_directory("/tmp"):
process_files_here()
# working directory is back to the original, guaranteed
Real-world example Running a subprocess or script that requires a specific working directory, without permanently affecting the calling code's environment.
Common follow-ups: Why is this pattern particularly risky to implement WITHOUT a context manager, in terms of leaking the directory change on error?
Exception Handling
ExitStack lets you enter() any number of context managers PROGRAMMATICALLY (in a loop, or conditionally), and guarantees ALL of them are properly exited (in reverse order) when the ExitStack itself exits — essential when the exact number of resources to manage isn't known until runtime, unlike a fixed 'with a, b, c:' statement.
from contextlib import ExitStack
filenames = ["a.txt", "b.txt", "c.txt"]
with ExitStack() as stack:
files = [stack.enter_context(open(fname)) for fname in filenames]
for f in files:
process(f)
# ALL files are properly closed here, however many there were
Real-world example Opening and safely managing a dynamic, runtime-determined number of files or database connections in a single batch operation.
Common follow-ups: How does ExitStack's callback() method let you register arbitrary cleanup functions, not just full context managers?
File I/O & Context Managers
Since @contextmanager already returns a _GeneratorContextManager instance supporting BOTH 'with cm():' usage AND direct '@cm()' decoration (via ContextDecorator), you get dual usage automatically — the decorated function's ENTIRE body runs wrapped inside the context manager's enter/exit logic.
from contextlib import contextmanager
@contextmanager
def suppress_and_log(*exceptions):
try:
yield
except exceptions as e:
logging.warning(f"Suppressed: {e}")
@suppress_and_log(ValueError)
def risky_operation():
raise ValueError("oops")
risky_operation() # logs a warning instead of crashing, thanks to decorator usage
Real-world example Building a single reusable error-suppression or logging utility usable either as an explicit 'with' block OR as a function decorator.
Common follow-ups: What is the specific base class (ContextDecorator) that enables this dual with/decorator usage pattern?
Decorators