with
The with statement in Python simplifies resource management by handling setup and cleanup automatically. It ensures that resources like files, database connections, or locks are properly acquired and released, even if errors occur during execution.
Syntax
with expression as variable:
# Block where variable is available
The expression must return a context manager — an object that implements __enter__ and __exit__ methods.
Basic Examples
Opening a File
The most common use case is file handling:
with open("example.txt", "w") as f:
f.write("Hello, World!")
# File is automatically closed when exiting the block
Reading a File
with open("example.txt", "r") as f:
content = f.read()
print(content)
# File is automatically closed after reading
The file is guaranteed to be closed, even if an error occurs while reading.
How It Works
Under the Hood
When you use with, Python calls:
__enter__()— runs at the start of the block, returns the value bound toas- Your code executes inside the block
__exit__()— runs when exiting the block, even if an exception occurred
class MyContext:
def __enter__(self):
print("Entering context")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("Exiting context")
return False
with MyContext() as ctx:
print("Inside block")
# Entering context
# Inside block
# Exiting context
The as Clause
The as clause binds the result of __enter__() to a variable:
with open("file.txt") as f:
# f is the file object returned by open().__enter__()
pass
You can skip the as clause if you don’t need the bound value:
with open("file.txt"):
# File is open, but no variable bound
pass
Multiple Context Managers
Stacking Multiple With Statements
with open("input.txt") as infile:
with open("output.txt", "w") as outfile:
outfile.write(infile.read())
Python 3.1+ — Multiple Context Managers
Python 3.1 introduced a more concise syntax:
with open("input.txt") as infile, open("output.txt", "w") as outfile:
outfile.write(infile.read())
Python 3.10+ — Parenthesized Context Managers
Python 3.10 allows line breaks:
with (
open("input.txt") as infile,
open("output.txt", "w") as outfile
):
outfile.write(infile.read())
Exception Handling
The __exit__ method receives exception information:
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is not None:
print(f"Exception occurred: {exc_val}")
# Return True to suppress the exception
# Return False (or None) to propagate it
return False
Suppressing Exceptions
Return True from __exit__ to suppress an exception:
class SuppressError:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
return True # Suppress all exceptions
with SuppressError():
raise ValueError("This will be suppressed")
# No error raised
Built-in Context Managers
open()
Files are context managers:
with open("file.txt") as f:
data = f.read()
threading.Lock()
Locks for thread synchronization:
import threading
lock = threading.Lock()
with lock:
# Only one thread can enter this block
critical_section()
decimal.localcontext()
Temporarily change decimal precision:
from decimal import Decimal, localcontext
with localcontext() as ctx:
ctx.prec = 2
result = Decimal("1.234") * Decimal("5.678")
print(result) # 7.0
contextlib.redirect_stdout()
Temporarily redirect standard output:
from contextlib import redirect_stdout
import io
buffer = io.StringIO()
with redirect_stdout(buffer):
print("This goes to the buffer")
print(buffer.getvalue())
Creating Custom Context Managers
Using a Class
class Timer:
def __enter__(self):
self.start = time.time()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.elapsed = time.time() - self.start
print(f"Elapsed: {self.elapsed:.2f}s")
with Timer():
time.sleep(1)
# Elapsed: 1.00s
Using contextlib.contextmanager
A decorator-based approach:
from contextlib import contextmanager
import time
@contextmanager
def timer():
start = time.time()
try:
yield
finally:
elapsed = time.time() - start
print(f"Elapsed: {elapsed:.2f}s")
with timer():
time.sleep(0.5)
# Elapsed: 0.50s
Using contextlib.AsyncExitStack
For async context managers:
from contextlib import asynccontextmanager
@asynccontextmanager
async def async_timer():
start = time.time()
try:
yield
finally:
print(f"Elapsed: {time.time() - start:.2f}s")
Common Patterns
Database Connections
import sqlite3
with sqlite3.connect("database.db") as conn:
cursor = conn.cursor()
cursor.execute("SELECT * FROM users")
results = cursor.fetchall()
# Connection automatically closed
Temporary Files
import tempfile
with tempfile.NamedTemporaryFile(mode="w", delete=False) as tmp:
tmp.write("Temporary content")
tmp_name = tmp.name
# File closed, but not deleted (delete=False)
Lock Management
import threading
counter = 0
lock = threading.Lock()
def increment():
global counter
with lock:
counter += 1
return counter
Best Practices
Always Use with for Resources
# Bad — file may not close on error
f = open("file.txt")
f.write("data")
f.close()
# Good — guaranteed cleanup
with open("file.txt", "w") as f:
f.write("data")
Avoid Long Operations in with Block
Keep the block short to minimize resource holding time:
# Good — file closed quickly
with open("large_file.txt") as f:
data = f.read()
process_data(data) # Outside the with block
Nest Only When Necessary
Deeply nested context managers can be hard to read:
# Hard to read
with open("a") as a, open("b") as b, open("c") as c:
pass
# Easier to read
with open("a") as a:
with open("b") as b:
with open("c") as c:
pass
See Also
- break keyword — exit loops early
- except keyword — exception handling
- open built-in — file opening function
- contextlib module — context manager utilities