File I/O & Context Managers

15 questions found

How do you open and read a text file using the 'with' statement, and why is 'with' preferred over manual open()/close()?

Beginner
'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

What is the difference between reading a file with .read(), .readline(), and .readlines()?

Beginner
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

What are the common file modes ('r', 'w', 'a', 'rb') passed to open(), and what does each one do?

Beginner
'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

How do you write and use a custom context manager by implementing __enter__ and __exit__?

Intermediate
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

How does @contextlib.contextmanager let you write a context manager using a generator function instead of a full class?

Intermediate
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

How do you use pathlib.Path for modern, object-oriented file path manipulation instead of os.path's string-based functions?

Intermediate
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

How do you use multiple context managers in a single 'with' statement, and how does this compare to nesting separate 'with' blocks?

Intermediate
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

How would you implement a context manager that temporarily changes the current working directory and restores it afterward?

Advanced
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

How do you implement contextlib.ExitStack to dynamically manage a VARIABLE number of context managers determined at runtime?

Advanced
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

How would you write a reusable context manager decorator using contextlib.contextmanager that ALSO works when applied directly to a function, like @contextmanager-decorated functions support?

Advanced
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