Use with statement
Learn to use the with statement for resource management in Python — a practical tutorial with hands-on steps, troubleshooting, and what to study next.
Focus: use with statement for resource management
Forgetting to close a file or release a database connection can lead to resource leaks, corrupted data, or even crashed applications. Python's with statement provides a clean, safe, and automatic way to manage resources — ensuring they are properly cleaned up even when errors occur.
The problem this lesson solves
When working with external resources like files, network sockets, or database connections, you must release them after use. Failing to do so can exhaust system limits (file descriptors, memory) or leave locked files. The traditional approach — manually calling close() — is error-prone: an exception after open() but before close() leaves the resource dangling.
# Without 'with' — error-prone
file = open('data.txt', 'w')
file.write('Hello')
# What if an exception happens here?
file.close() # Never runs if exception!
The with statement solves this by guaranteeing cleanup, no matter how the block exits.
Core concept / mental model
Think of a context manager as a resource guard that you enter and automatically leave. The with statement:
- Enters the context (acquires the resource).
- Executes your code block.
- Exits the context (releases the resource) — even if an exception occurs.
Pro tip: Any object that implements the context manager protocol (methods
__enter__and__exit__) can be used withwith. Python's built-inopen()function returns a file object that does exactly this.
Key vocabulary
- Context manager: Object that defines
__enter__and__exit__. - Cleanup: The release of resources (close file, release lock, close socket).
- Exception safety: The guarantee that cleanup happens even on errors.
How it works step by step
When you write:
with expression as variable:
# code block
Python does the following in order:
- Evaluate the expression (e.g.,
open('file.txt')) to get a context manager. - Call the context manager's
__enter__()method. Its return value is bound tovariable(optional if you useas). - Execute the code block.
- Call the context manager's
__exit__()method — regardless of whether the block succeeded or raised an exception. If an exception occurred,__exit__receives the exception type, value, and traceback. - Propagates the exception, unless
__exit__returnsTrue(which suppresses it).
Visual example
class ManagedFile:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
def __enter__(self):
self.file = open(self.filename, self.mode)
return self.file # bound to 'as' variable
def __exit__(self, exc_type, exc_val, exc_tb):
if self.file:
self.file.close()
# Log error if needed
print(f"Exiting. Exception? {exc_type is not None}")
with ManagedFile('test.txt', 'w') as f:
f.write('Hello!')
# raise Exception('Oops') # Uncomment to test
print('File closed automatically.')
# Output (no exception):
# Exiting. Exception? False
# File closed automatically.
Hands-on walkthrough
Example 1: Working with files
# Write to a file safely
with open('log.txt', 'w') as f:
f.write('2025-01-01: App started\n')
f.write('2025-01-01: User logged in\n')
# At this point, file is automatically closed
# Read the file back
with open('log.txt', 'r') as f:
content = f.read()
print(content)
Expected output:
2025-01-01: App started
2025-01-01: User logged in
Example 2: Multiple resources with nested with
with open('source.txt', 'r') as src:
with open('dest.txt', 'w') as dest:
for line in src:
dest.write(line.upper())
Alternatively, combine them in one line (Python 3.10+):
with open('source.txt', 'r') as src, open('dest.txt', 'w') as dest:
for line in src:
dest.write(line.upper())
Example 3: Locking with threading.Lock
import threading
lock = threading.Lock()
shared_resource = []
def worker(item):
with lock: # Automatically acquire and release
shared_resource.append(item)
# Critical section
threads = [threading.Thread(target=worker, args=(i,)) for i in range(5)]
for t in threads:
t.start()
for t in threads:
t.join()
print(shared_resource) # [0, 1, 2, 3, 4] (order depends)
Example 4: Custom context manager using contextlib
from contextlib import contextmanager
@contextmanager
def temp_file(filename):
print('Creating temp file...')
f = open(filename, 'w')
try:
yield f
finally:
f.close()
print('Temp file closed.')
with temp_file('temp.txt') as f:
f.write('Temporary data')
# Output:
# Creating temp file...
# Temp file closed.
Compare options / when to choose what
| Approach | Use Case | Cleanup Guarantee | Readability |
|---|---|---|---|
with statement |
Files, locks, connections | ✅ Automatic — even on exceptions | ✅ High — clear scope |
Manual try/finally |
Legacy code, non-with objects |
✅ Guaranteed but verbose | ⚠️ OK — more lines |
contextlib.contextmanager |
Custom reusable context managers | ✅ Depends on implementation | ✅ High — decorator simplifies |
When to choose what:
- Always prefer with for any resource that supports it (files, locks, database connections, subprocesses, sockets).
- Use contextlib.contextmanager when you need a custom resource manager without writing a full class.
- Avoid manual try/finally unless you're dealing with an object that doesn't implement the context manager protocol.
Troubleshooting & edge cases
1. Forgetting as when you need the resource
# Wrong — you can't use the file object
with open('data.txt', 'r'): # No 'as'
content = file.read() # NameError: name 'file' is not defined
# Correct
with open('data.txt', 'r') as f:
content = f.read()
2. Suppressing exceptions in custom __exit__
class SilentExit:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
return True # Suppresses any exception
with SilentExit():
raise ValueError("Oops")
print("Exception was swallowed!") # Runs – exception silenced
Fix: Only suppress exceptions deliberately (e.g., when logging is sufficient).
3. File not closed when with block exits via break in a loop
for i in range(5):
with open('data.txt', 'w') as f:
f.write(str(i))
if i == 2:
break # File still closed correctly
4. Resource not released if __enter__ raises
If open() itself fails (e.g., permission denied), __exit__ is not called because the context was never entered. This is correct — no resource was acquired.
What you learned & what's next
You now understand:
- What the with statement is and the problem it solves.
- How context managers work under the hood (__enter__ / __exit__).
- How to use with for files, locks, and custom resources.
- How to choose between with, try/finally, and contextlib.
- How to troubleshoot common pitfalls.
Next step: In the upcoming lesson, you'll learn about exception handling with try/except — the mechanism Python uses to gracefully recover from runtime errors. Together with with, you'll be able to write robust, production-ready code.
Practice recap
Create a custom context manager that measures the execution time of a code block. Use the @contextmanager decorator from contextlib. Your manager should print the elapsed time after the block completes. Test it with a time.sleep(2) call.
Common mistakes
- Forgetting to use
as variablewhen you need to reference the resource later, causing aNameError. - Assuming
__exit__is called if__enter__fails — it is not. The context is never entered. - Accidentally suppressing exceptions by returning
Truein a custom__exit__method without intending to silence errors.
Variations
- Using
contextlib.closing()for legacy objects that have aclose()method but no context manager. - Using
contextlib.suppress()to explicitly ignore specific exceptions within a context. - Combining multiple resources in one
withstatement using comma-separated contexts (Python 3.10+).
Real-world use cases
- Automatically closing file handles in a web scraper that writes hundreds of page contents to disk.
- Releasing database connections in an API endpoint using
with connection.cursor() as cursor. - Acquiring and releasing a threading lock in a multi-threaded cache update routine.
Key takeaways
- The
withstatement guarantees resource cleanup (close file, release lock) even when exceptions occur. - Use
with open(...) as f:instead of manualtry/finallyfor files — shorter and safer. - Your own classes can be context managers by implementing
__enter__and__exit__. - Use
contextlib.contextmanagerfor quick custom context managers without writing a full class. - Multiple resources can be managed in one line with
with expr1 as a, expr2 as b:. - Never suppress exceptions unintentionally in
__exit__— returnFalse(default) to propagate errors.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.