Python • Python-Specific Topics

Context Managers (with) — Use context managers (with) for resource management

Learn how Python's with statement automatically manages resources so you never forget to clean up.

Code braces with terminal and markup symbols representing Python programming.
Inside this lesson 10 sections 5 questions 5 figures

SECTION 1

Historical Context & Motivation

Every time your program opens a file, connects to a database, or grabs a network socket, it is borrowing a resource from the operating system. If your code forgets to return that resource — by closing the file or disconnecting — you create a resource leak. Leaked resources pile up, slow down programs, and can even crash your computer. Early programming languages left it entirely up to the programmer to remember every cleanup step, which was a constant source of bugs.

Python's creators recognized this pain point and looked at how other languages handled it. C++ had destructors, Java had try-finally blocks, and C# introduced a using statement. Python borrowed the best ideas and rolled them into a clean, readable feature: the with statement, powered by a protocol called the context manager.

2000 • Python 2.0 Released

Python 2.0 arrives with garbage collection, but resource cleanup still relies on manual try/finally blocks that programmers often forget to write.

2005 • PEP 343 Proposed

Guido van Rossum and others draft PEP 343 — "The 'with' Statement" — proposing a standard protocol for guaranteed resource cleanup using __enter__ and __exit__ methods.

2006 • Python 2.5 Ships with

The with statement becomes available (via from __future__ import with_statement) and the contextlib module is added to the standard library.

2008 • Python 3.0 Makes it Standard

Python 3.0 makes with a first-class keyword with no import needed. It rapidly becomes the recommended way to handle files, locks, and connections.

2019 • Parenthesized Context Managers

Python 3.10 introduces parenthesized context managers, letting you open multiple resources in a single with block across multiple lines for better readability.

The key question that context managers answer is straightforward: How can we guarantee cleanup code always runs, even when errors occur, without burdening the programmer with boilerplate? The with statement is Python's elegant answer.

SECTION 2

Core Principles & Definitions

At its heart, a context manager is any Python object that defines two special methods: __enter__ and __exit__. When you write a with statement, Python automatically calls __enter__ at the start and __exit__ at the end — no matter what happens inside the block, even if an exception is raised.

1

Setup (__enter__)

The __enter__ method runs before the block. It acquires the resource (opens a file, starts a timer, etc.) and returns a value you can assign with as.
2

Body (the indented block)

Your main code runs inside the indented block. You work with the resource freely, knowing that cleanup is guaranteed once you leave this block.
3

Teardown (__exit__)

The __exit__ method runs after the block finishes — or after an exception. It releases the resource (closes the file, releases the lock, etc.).
4

Exception Safety

If an error occurs inside the block, __exit__ still runs. It receives the exception info and can choose to suppress it or let it propagate.
✦

KEY TAKEAWAY

Think of a context manager like a library checkout system. When you check out a book (__enter__), the system records it. When you return the book (__exit__), it's automatically logged back in — even if you lost your library card along the way. The with statement is the librarian making sure every book always gets returned.

SECTION 3

Visual Explanation — The with Statement Lifecycle

The with Statement Lifecyclewith open('data.txt') as f:Python evaluates the expression after "with"Step 1__enter__() is calledResource acquired • Return value bound to 'f'Step 2Indented Block Executescontent = f.read()Your code runs here • Exceptions may occurError?Success__exit__() is ALWAYS calledResource released • Exception info passed if error occurredCleanup is guaranteed — no matter what happens in the block
This diagram shows the three-phase lifecycle of a with statement. The expression is evaluated first, then __enter__ acquires the resource. After the indented block runs (whether it succeeds or raises an error), __exit__ always executes to release the resource.

The diagram above illustrates why the with statement is so powerful. Notice that both the success path and the error path converge on __exit__. This means your cleanup code is never skipped, even if your program crashes mid-way through the block. Without with, you would need a try/finally block to achieve the same guarantee, which requires more lines of code and is easier to mess up.

SECTION 4

How It Works — The Context Manager Protocol

Under the hood, the with statement follows a precise sequence that Python translates from a few clean lines into the equivalent of a try/finally block. Understanding this translation helps you see why context managers are both simpler to write and safer to use.

The Equivalent try/finally Code

When you write:

ℹ

with Statement Syntax

with open('data.txt', 'r') as f: content = f.read() print(content)

Python internally translates this to something like:

📝

Equivalent try/finally

manager = open('data.txt', 'r') f = manager.__enter__() try: content = f.read() print(content) finally: manager.__exit__(exc_type, exc_val, exc_tb)

The finally block is the key to understanding why cleanup always happens. In Python, a finally clause runs no matter what — whether the code succeeds, raises an exception, or even uses return to exit early. The with statement gives you this safety net automatically.

The __exit__ Method's Parameters

The __exit__ method receives three arguments: exc_type (the exception class, or None), exc_val (the exception instance, or None), and exc_tb (the traceback, or None). If no error occurred, all three are None. If __exit__ returns True, it suppresses the exception; otherwise, the exception propagates normally.

💡

Writing Your Own Context Manager

class Timer: def __enter__(self): import time self.start = time.time() return self def __exit__(self, exc_type, exc_val, exc_tb): import time elapsed = time.time() - self.start print(f'Elapsed: {elapsed:.2f}s') return False # Don't suppress exceptions with Timer(): # code you want to time total = sum(range(1_000_000))

SECTION 5

Common Patterns & Built-in Context Managers

Python provides many built-in objects that already work as context managers. You have likely used the most common one — open() — but the pattern extends to many other resources. The table below shows some of the most useful built-in context managers you will encounter.

Context ManagerWhat It ManagesWhat __exit__ Does
open()File handlesCloses the file automatically
threading.Lock()Thread locksReleases the lock
socket.socket()Network socketsCloses the socket connection
tempfile.TemporaryFile()Temporary filesDeletes the temporary file
decimal.localcontext()Decimal precision settingsRestores original precision
contextlib.suppress()Exception handlingSilently suppresses specified exceptions

Common built-in context managers in Python

Without vs. With Context Managers❌ Without withf = open('data.txt')try: content = f.read() process(content)except IOError: print('Error!')finally: f.close()8 lines of codeMust remember finallyMust remember f.close()Easy to forget cleanupBug if open() fails before try✅ With withwith open('data.txt') as f: content = f.read() process(content)3 lines of code✓ Auto-closes file✓ Exception-safe✓ Cleaner and shorter✓ No forgotten cleanupPythonic! 🐍
Side-by-side comparison of file handling without a context manager (left, 8 lines with potential bugs) versus with a context manager (right, 3 lines, fully safe).

Using contextlib for Quick Context Managers

Python's contextlib module provides a shortcut for creating context managers without writing a full class. The @contextmanager decorator lets you use a generator function instead. You put setup code before a yield statement and cleanup code after it.

💡

contextlib Example

from contextlib import contextmanager @contextmanager def managed_file(filename): f = open(filename, 'r') try: yield f # This value is bound by 'as' finally: f.close() # Cleanup always runs with managed_file('notes.txt') as f: print(f.read())

SECTION 6

Worked Example — Building a Database Connection Manager

Let's walk through a complete example of building a custom context manager for a SQLite database connection. This is a realistic scenario you might encounter in a school project that needs to store and retrieve data.

Custom Context Manager: Database Connection

  1. 1

    Step 1 — Define the Class with __init__

    First, we create a class that accepts the database filename. The __init__ method stores the filename but does not open the connection yet — we save that for __enter__.
    class DatabaseConnection: def __init__(self, db_name): self.db_name = db_name self.connection = None
  2. 2

    Step 2 — Implement __enter__

    The __enter__ method opens the database connection using Python's built-in sqlite3 module and returns the connection object so it can be used inside the with block.
    def __enter__(self): import sqlite3 self.connection = sqlite3.connect(self.db_name) return self.connection
  3. 3

    Step 3 — Implement __exit__

    The __exit__ method checks whether an exception occurred. If everything went smoothly, it commits changes. If there was an error, it rolls back. Either way, it closes the connection.
    def __exit__(self, exc_type, exc_val, exc_tb): if exc_type is None: self.connection.commit() else: self.connection.rollback() self.connection.close() return False # Don't suppress exceptions
  4. 4

    Step 4 — Use the Context Manager

    Now we can use our custom context manager with the with statement. The connection opens, we execute queries, and cleanup happens automatically — commit on success, rollback on error.
    with DatabaseConnection('school.db') as conn: cursor = conn.cursor() cursor.execute('CREATE TABLE IF NOT EXISTS students (name TEXT, grade INT)') cursor.execute("INSERT INTO students VALUES ('Alice', 11)") # Connection commits and closes automatically!
  5. 5

    Step 5 — Verify Cleanup

    After the with block, the connection is guaranteed to be closed. If we try to use conn again, Python will raise an error — proof that cleanup happened.
    # Outside the with block: # conn.cursor() # Raises ProgrammingError: closed database — Cleanup confirmed!

SECTION 7

Strengths, Limitations & Comparisons

Context managers are one of Python's most beloved features, but like any tool, they have trade-offs. Understanding when to use them — and when they might not be the right fit — will help you write better code.

AspectStrengths ✅Limitations ⚠️
ReadabilityCode is concise and clear — the intent is immediately obviousCustom context managers can add complexity if the class is too large
SafetyGuaranteed cleanup even during exceptions; eliminates resource leaksSuppressing exceptions in __exit__ can hide bugs if done carelessly
FlexibilityWorks with files, locks, connections, timers, and any custom resourceNot useful for resources that don't need deterministic cleanup
ScopeThe resource's lifetime is visually clear from the indentationThe resource cannot be used outside the with block (by design)
Learning CurveUsing built-in context managers is very simple — just add 'with'Writing custom __enter__/__exit__ requires understanding the protocol

Strengths and limitations of context managers

✦

KEY TAKEAWAY

Context managers follow the same idea as an automatic car wash. You drive in (__enter__), the wash runs (body block), and the system dries and finishes (__exit__) — even if the soap dispenser malfunctions mid-wash. You don't have to remember to turn anything off because the system handles the beginning and end automatically.

SECTION 8

Connections to Advanced Topics

Context managers connect to several more advanced Python features. As you continue learning, you'll see this same pattern — setup and cleanup — appear in many places. The table below shows how context managers relate to topics you may encounter next.

Context Manager ConceptAdvanced ExtensionConnection
__enter__ / __exit__Async context managersPython's async with uses __aenter__ / __aexit__ for async resources
@contextmanagerDecorators & generatorsCombines generator protocol (yield) with decorator syntax — two powerful patterns in one
Resource managementRAII pattern (C++)Context managers implement a Python version of Resource Acquisition Is Initialization, a foundational software design pattern
Exception handling in __exit__Exception groups (Python 3.11+)Newer Python versions add ExceptionGroup for handling multiple simultaneous exceptions in context managers

How context managers connect to advanced Python topics

In web development frameworks like Django and Flask, context managers are used behind the scenes to manage database sessions, HTTP connections, and application contexts. In data science, libraries like pandas and matplotlib use context managers for file I/O and plot styling. Mastering the with statement now gives you a foundation that transfers directly to these professional tools.

💡

Looking Ahead

Once you're comfortable with basic with statements, try exploring contextlib.ExitStack — it lets you dynamically manage a variable number of context managers, which is very useful when you don't know how many files or connections you'll need until runtime.

SECTION 9

Practice Problems

Question 1: CONCEPTUAL

Explain in your own words why the with statement is safer than manually calling file.close() after you're done with a file. What could go wrong without with?
Show answer
When you manually call file.close(), any exception that occurs between opening and closing the file will cause close() to be skipped entirely. The program crashes, and the file remains open, potentially locking it or losing buffered data. The with statement guarantees that __exit__ (which calls close()) always runs — even if an exception is raised — because it internally uses a try/finally structure.

Question 2: BASIC

Rewrite the following code to use a with statement: f = open('scores.txt', 'w') f.write('Alice: 95\n') f.write('Bob: 87\n') f.close()
Show answer
The rewritten code uses the with statement to automatically close the file: with open('scores.txt', 'w') as f: f.write('Alice: 95\n') f.write('Bob: 87\n') The open() call becomes the context manager expression, and as f binds the file object returned by __enter__. No explicit close() is needed because __exit__ handles it automatically.

Question 3: INTERMEDIATE

Write a context manager class called Indenter that prints text with increasing indentation each time it's nested. For example: with Indenter() as indent: indent.print('Hello') with indent: indent.print('World') with indent: indent.print('!') Should output: Hello World !
Show answer
Here is a working implementation: class Indenter: def __init__(self): self.level = 0 def __enter__(self): self.level += 1 return self def __exit__(self, exc_type, exc_val, exc_tb): self.level -= 1 return False def print(self, text): print(' ' * (self.level - 1) + text) Each time __enter__ is called, the indent level increases by 1. When __exit__ runs, it decreases. The print method uses the current level to determine how many spaces to prepend. This pattern shows that context managers can manage state, not just external resources.

Question 4: APPLIED

You are building a student grade tracker. Write a context manager using @contextmanager from contextlib that opens a CSV file, writes a header row ('Name,Grade'), yields the file writer, and guarantees the file is closed. Then use it to write three student records.
Show answer
Here is the solution using the @contextmanager decorator: from contextlib import contextmanager import csv @contextmanager def grade_file(filename): f = open(filename, 'w', newline='') writer = csv.writer(f) writer.writerow(['Name', 'Grade']) try: yield writer finally: f.close() with grade_file('grades.csv') as writer: writer.writerow(['Alice', 95]) writer.writerow(['Bob', 87]) writer.writerow(['Carol', 92]) The code before yield acts as __enter__ (setup), the yielded value is bound to writer via as, and the finally block after yield acts as __exit__ (cleanup).

Question 5: CRITICAL THINKING

Consider a context manager whose __exit__ method returns True. What happens if an exception occurs inside the with block? Is this behavior ever useful? Give a scenario where suppressing an exception would be appropriate, and another where it would be dangerous.
Show answer
When __exit__ returns True, it tells Python to suppress (swallow) the exception — the program continues after the with block as if nothing went wrong. This can be useful in specific cases, such as suppressing a FileNotFoundError when trying to delete a temporary file that may or may not exist (Python's contextlib.suppress(FileNotFoundError) does exactly this). However, it would be dangerous if used broadly — for example, if you suppress all exceptions in a database context manager, you might hide data corruption errors. The user's data could be silently lost while the program happily continues. The rule of thumb: only suppress specific, expected exceptions, and never suppress broad categories like Exception.

SUMMARY

Lesson Summary

Summary

Python's with statement provides a clean, readable way to manage resources by leveraging the context manager protocol — a pair of special methods called __enter__ and __exit__. The __enter__ method sets up the resource and returns it, while __exit__ guarantees cleanup runs no matter what — even if an exception is raised inside the block. This pattern eliminates resource leaks and replaces verbose try/finally blocks with a single elegant line.

Built-in context managers like open() handle files, while you can create your own using either a class with __enter__/__exit__ or the @contextmanager decorator from contextlib. Whether you're managing files, database connections, timers, or locks, the with statement ensures your code is shorter, safer, and more Pythonic.

Varsity Tutors • Python • Context Managers (with) — Use context managers (with) for resource management