Context Manager
Python Context Managers are most commonly used with the with
statement to manage resources like files, network connections, or locks—anything that requires setup and teardown logic.
🔥 Basic Example – File Handling
with open("hello.txt", "w") as f:
f.write("Hello, World!")
# File is automatically closed here
open()
returns a context manager.- When the
with
block exits,f.close()
is called automatically—even if an exception happens inside the block.
🧙♂️ Custom Context Manager (Class-based)
You can create your own using __enter__()
and __exit__()
methods:
class MyContext:
def __enter__(self):
print("Entering context")
return self # Optional
def __exit__(self, exc_type, exc_val, exc_tb):
print("Exiting context")
with MyContext():
print("Inside context")
Output:
Entering context
Inside context
Exiting context
🧼 Handling Exceptions Inside a Context Manager
class SafeOpener:
def __init__(self, filename):
self.filename = filename
def __enter__(self):
self.file = open(self.filename, 'w')
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
if self.file:
self.file.close()
if exc_type:
print(f"Exception handled: {exc_val}")
return True # Suppress the exception
with SafeOpener("output.txt") as f:
f.write("Safe writing...")
raise ValueError("Something went wrong") # This won't crash
The with
statement (i.e., context manager) works with objects (not just functions) that implement the context management protocol—i.e., they must define:
__enter__(self)
__exit__(self, exc_type, exc_val, exc_tb)
But you can also create function-based context managers using @contextmanager
from the contextlib
module.
🧵 Using contextlib
(Cleaner Way)
from contextlib import contextmanager
@contextmanager
def open_file(name, mode):
f = open(name, mode)
try:
yield f
finally:
f.close()
with open_file("data.txt", "w") as f:
f.write("Hello from contextlib!")
🔧 Example 1: A Timer Function Context Manager
Let's create a timer function that calculate the execution duration of a function
import time
from contextlib import contextmanager
@contextmanager
def timer():
start = time.time()
yield # Do the work in between
end = time.time()
print(f"Elapsed: {end - start:.2f} seconds")
# usage
with timer():
time.sleep(2)
🧹 Example 2: Changing Directory Temporarily
import os
from contextlib import contextmanager
@contextmanager
def change_dir(destination):
original = os.getcwd()
os.chdir(destination)
try:
yield
finally:
os.chdir(original)
with change_dir("/tmp"):
print("Current directory:", os.getcwd())
Let's walk through how to turn a regular function into a context manager using @contextmanager
from Python’s contextlib
module.
🪄 Step-by-Step Example
Let's say we have a simple function that opens a file, and we want to make it safe and elegant using a context manager.
❌ Regular Function (not context-safe)
def write_file(filename, content):
f = open(filename, 'w')
f.write(content)
f.close()
If something goes wrong between open()
and close()
, the file might not be closed properly.
✅ Turn it into a Context Manager Function
from contextlib import contextmanager
@contextmanager
def open_file_safe(filename, mode):
f = open(filename, mode)
try:
yield f # Give control to the block using 'with'
finally:
f.close() # Always close, even on error
✅ Now use it like this:
with open_file_safe('hello.txt', 'w') as f:
f.write('Hello from context manager!')
Behind the scenes:
open_file_safe()
is calledf = open(...)
runs beforeyield
- Code inside the
with
block runs whereyield
is - After block finishes (or error occurs),
finally
ensuresf.close()
runs
🧪 Example 3: Temporarily Redirect Output
from contextlib import contextmanager
import sys
@contextmanager
def silence_stdout():
original = sys.stdout
sys.stdout = open('/dev/null', 'w')
try:
yield
finally:
sys.stdout.close()
sys.stdout = original
with silence_stdout():
print("You won't see this.")
print("But you'll see this!")
✅ Using a Context Manager with SQLite
import sqlite3
def create_table():
with sqlite3.connect('my_database.db') as conn:
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
name TEXT
)
''')
conn.commit()
This means:
- No need to manually call
conn.close()
- It’s safe even if an exception is raised inside the block
Want More Control? Create Your Context Manager for DB
from contextlib import contextmanager
@contextmanager
def db_cursor(db_name):
conn = sqlite3.connect(db_name)
cursor = conn.cursor()
try:
yield cursor
conn.commit()
except Exception:
conn.rollback()
raise
finally:
conn.close()
Usage:
with db_cursor('my_database.db') as cursor:
cursor.execute("SELECT * FROM users")
print(cursor.fetchall())
💡 Summary
You can call any function-based context manager as long as:
- It's decorated with
@contextmanager
- It contains a
yield
statement that splits setup and teardown
🧠 It's worth reading about the similarities and dissimilarities between Context Manager and Decorator
Thanks for Stopping by! Happy learning
Connect with me on