Python Context Managers
Table of Contents
In the last lesson, we learned about Python File Paths. After learning about file paths, let us learn about context managers in Python. We have been using the with statement for files. Now let us understand how it works and how to create our own.
In this tutorial, we will learn what context managers are, why they are useful, and how to create them using classes and contextlib.
What Is a Context Manager in Python?
A context manager is an object that sets something up and tears it down. It handles the βbeforeβ and βafterβ automatically.
The with statement is how we use a context manager.
with open("file.txt", "r") as file: content = file.read()Here, open("file.txt", "r") is a context manager. It opens the file. Then it gives us the file object. When the block ends, it closes the file. Even if an exception happens.
That is the magic of context managers. They handle cleanup for us.
Why Use Context Managers in Python?
Without context managers, we have to close files manually.
file = open("file.txt", "r")try: content = file.read()finally: file.close()That works. But it is verbose. And we might forget to close the file.
With a context manager, we just write:
with open("file.txt", "r") as file: content = file.read()Much cleaner. And safer.
How Does the with Statement Work in Python?
The with statement works with any object that has __enter__ and __exit__ methods.
__enter__is called at the start of the block. It sets things up.__exit__is called at the end of the block. It cleans things up.
When we write:
with some_object as variable: # do somethingPython does this:
- Calls
some_object.__enter__()and assigns the result tovariable. - Runs the block.
- Calls
some_object.__exit__()at the end. Even if an exception happens.
That is how it works.
How to Create a Context Manager Using a Class in Python
We can create our own context manager by defining a class with __enter__ and __exit__.
Letβs create a simple context manager that prints a message before and after.
class MyContext: def __enter__(self): print("Entering the block") return self
def __exit__(self, exc_type, exc_value, traceback): print("Exiting the block")
with MyContext() as context: print("Inside the block")Output
Entering the blockInside the blockExiting the blockThat is the basic pattern.
The __exit__ method takes three arguments: the exception type, the exception value, and the traceback. If no exception happened, they are all None.
We can use them to handle exceptions.
class MyContext: def __enter__(self): print("Entering") return self
def __exit__(self, exc_type, exc_value, traceback): if exc_type: print(f"An exception occurred: {exc_value}") print("Exiting") return False # do not suppress the exception
with MyContext() as context: print("Inside") raise ValueError("Something went wrong")Output
EnteringInsideAn exception occurred: Something went wrongExitingTraceback (most recent call last): ...ValueError: Something went wrongThe __exit__ method ran. It printed the exception message. Then it returned False, so the exception was not suppressed. The program still crashed.
If we return True, the exception is suppressed.
class MyContext: def __enter__(self): return self
def __exit__(self, exc_type, exc_value, traceback): if exc_type: print(f"Caught: {exc_value}") return True # suppress the exception
with MyContext() as context: raise ValueError("Something went wrong")
print("After the block")Output
Caught: Something went wrongAfter the blockThe exception was caught and suppressed. The program continued.
Python Context Manager Example β Timing a Block
Letβs create a context manager that measures how long a block takes.
import time
class Timer: def __enter__(self): self.start = time.time() return self
def __exit__(self, exc_type, exc_value, traceback): self.end = time.time() print(f"Elapsed time: {self.end - self.start:.4f} seconds")
with Timer(): total = 0 for i in range(1000000): total += iOutput
Elapsed time: 0.0452 secondsThat is a useful context manager.
How to Use contextlib for Simpler Context Managers in Python
Python has a module called contextlib. It provides a decorator called @contextmanager that lets us write context managers as functions.
from contextlib import contextmanager
@contextmanagerdef my_context(): print("Entering") yield print("Exiting")
with my_context(): print("Inside")Output
EnteringInsideExitingThat is much simpler than writing a class.
We can also handle exceptions.
from contextlib import contextmanager
@contextmanagerdef my_context(): print("Entering") try: yield except Exception as e: print(f"Caught: {e}") finally: print("Exiting")
with my_context(): print("Inside") raise ValueError("Something went wrong")Output
EnteringInsideCaught: Something went wrongExitingThe exception is caught inside the context manager. But it is not suppressed. So the program would still crash after the block.
If we want to suppress it, we can use return after the except.
@contextmanagerdef my_context(): print("Entering") try: yield except Exception as e: print(f"Caught: {e}") return finally: print("Exiting")Now the exception is suppressed.
Python Context Manager Example β Database Connection
from contextlib import contextmanager
@contextmanagerdef database_connection(): print("Connecting to database") connection = "fake_connection" try: yield connection finally: print("Closing connection")
with database_connection() as conn: print(f"Using {conn}")Output
Connecting to databaseUsing fake_connectionClosing connectionThat is a clean way to manage resources.
Common Mistakes with Context Managers in Python
Mistake 1: Forgetting to use with
file = open("data.txt", "r")content = file.read()# forgot to closeFix: Use with.
with open("data.txt", "r") as file: content = file.read()Mistake 2: Returning True from exit without thinking
If we return True from __exit__, we suppress the exception. That might hide bugs.
Fix: Only return True if we really want to suppress the exception.
Mistake 3: Not handling exceptions in exit
class MyContext: def __enter__(self): return self
def __exit__(self, exc_type, exc_value, traceback): # no handling passIf an exception happens, it propagates. That is fine. But if we want to log it or handle it, we should do it here.
Mistake 4: Using contextlib without importing
@contextmanagerdef my_context(): yieldThis gives an error if we did not import contextmanager.
Fix:
from contextlib import contextmanagerPython Context Manager Example β Temporary File
Letβs build a context manager that creates a temporary file and deletes it when done.
import osfrom contextlib import contextmanager
@contextmanagerdef temporary_file(filename): with open(filename, "w") as file: file.write("Temporary data") print(f"Created {filename}") try: yield filename finally: os.remove(filename) print(f"Deleted {filename}")
with temporary_file("temp.txt") as fname: print(f"Using {fname}") with open(fname, "r") as file: print(file.read())Output
Created temp.txtUsing temp.txtTemporary dataDeleted temp.txtThat is a useful context manager. It creates a file, lets us use it, and deletes it when we are done.
Context managers are a powerful tool. They keep our code clean and safe. And with contextlib, they are easy to write.
Next, we will learn Python Modules, so we can split Python code into separate files and reuse it cleanly.