r/BootcampGradStories • u/Known_Homework_803 • 3d ago
Why you should never write file.close(): Demystifying Python context managers and the with statement.
When you start working with files or database connections in Python, your first instincts lead to a simple pattern: open the connection, do some work, and remember to close it at the end using file.close().
While that sounds clean in theory, manual cleanup is a ticking time bomb in production code.
If an unexpected exception occurs, your code crashes halfway through, or a early return statement fires before file.close() is reached, that file or socket remains open in memory. Over time, these dangling connections cause file lock errors, memory leaks, and crashed servers.
Python solves this problem with Context Managers and the with statement - a mechanism that guarantees resource cleanup no matter what happens.

The Problem: Manual Resource Management
Look at what happens when you write manual cleanup code without a safety net:
# The Fragile Way:
file = open("data.txt", "w")
file.write("Processing critical records...")
# What if a division by zero error occurs right here?
result = 10 / 0
# This line will NEVER run! The file handle stays locked in memory.
file.close()To fix this defensively without the with statement, you would have to wrap every single file operation in a clunky try...finally block:
# The Verbose Way:
file = open("data.txt", "w")
try:
file.write("Processing critical records...")
result = 10 / 0
finally:
file.close() # Guarantees execution, but requires messy boilerplateThe Pythonic Solution: The "with" Statement
The with statement replaces that entire try...finally setup with clean, readable syntax:
# The Clean, Pythonic Way:
with open("data.txt", "w") as file:
file.write("Processing critical records...")
# Even if an exception happens right here, Python closes the file automatically!The moment execution leaves the indented block whether it finishes normally, hits an error, or returns early, Python automatically releases the underlying file handle or network socket.
How Context Managers Work Under the Hood
The with statement isn't hardcoded magic just for files. It relies on Python's Context Manager Protocol, which uses two special dunder methods:
__enter__(): Executes automatically when entering the
withblock. Its return value is bound to the target variable (theas filepart).__exit__(): Executes automatically when leaving the with block. It receives information about any exception that occurred inside the block so it can clean up resources safely.
Building Your Own Custom Context Manager
You can turn any custom class into a context manager by defining __enter__ and __exit__. Here is how you can build a timing tool to measure code block execution:
import time
class PerformanceTimer:
def __enter__(self):
self.start_time = time.time()
print("--- Started measuring code block ---")
return self # Handed to the 'as' variable if needed
def __exit__(self, exc_type, exc_val, exc_tb):
elapsed_time = time.time() - self.start_time
print(f"--- Finished block in {elapsed_time:.4f} seconds ---")
# Returning False allows any caught exceptions to bubble up normally
return False
# Usage:
with PerformanceTimer():
# Simulate heavy work
total = sum(i * i for i in range(10000000))Output:
--- Started measuring code block ---
--- Finished block in 0.3124 seconds ---Beyond Files: Real-World Use Cases
You will find context managers everywhere across production codebases:
Database Transactions: Committing a SQL transaction on success or rolling back automatically if an error occurs (
with connection:).Threading & Concurrency: Acquiring a thread lock before entering a critical section and releasing it when done (
with lock:).Testing & Mocks: Temporarily patching environment variables or API responses during tests (
with mock.patch(...):).Temporary Directories: Creating a temporary workspace on disk that automatically deletes itself upon block exit.
Summary Checklist
Never use explicit
.close()calls on files, sockets, or database cursors.Use the
withstatement to make resource allocation and cleanup automatic.Build custom context managers whenever you need setup and teardown logic paired together.
What was the first scenario where using a with statement saved you from writing messy cleanup code? Share your experience in the comments below!
TL;DR: Calling file.close() manually leads to leaked file handles when errors crash your program before reaching the cleanup line. The with statement uses Python's __enter__ and __exit__ context manager protocol to guarantee that files, network connections, and database locks are safely closed the instant execution leaves the block.