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

Showing 1–10 of 15