Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes

Context Managers & 'with'

Learn to use context managers and the 'with' statement in Python for resource management, with hands-on exercises and troubleshooting.

Focus: work with context managers and the 'with' statement

Sponsored

You've written code that opens files, locks threads, or connects to databases, but you're tired of manually closing them — and one forgotten f.close() can corrupt data or leak memory. That's the exact pain context managers solve: they automatically set up and tear down resources, no matter what. By the end of this lesson, you'll confidently write clean, safe Python with the with statement and even build your own context managers.

The problem this lesson solves

Imagine you're reading a large log file. Without a context manager, you must remember to close the file every time:

f = open('server.log', 'r')
data = f.read()
f.close()  # easy to forget or skip in an exception

If an exception occurs before f.close(), the file stays open. Open files consume system handles, and on Windows you can't delete or move them. Over time, leaked handles crash applications. Context managers eliminate this headache by guaranteeing cleanup.

Core concept / mental model

A context manager is an object that defines two magic methods: __enter__ and __exit__. The with statement calls __enter__ at the start and __exit__ at the end, even if an error happens. Think of it like a hotel key: you enter the room, get the key (__enter__), and when you leave, you exit and return the key (__exit__) — no matter why you left.

Pro tip: Every resource that needs setup/teardown (files, locks, database connections, network sockets) should be wrapped with with.

How it works step by step

  1. Python evaluates the expression after with — this must return a context manager.
  2. The context manager's __enter__ method runs — its return value is assigned to the as variable (if used).
  3. The indented block executes — your main code runs here.
  4. When the block finishes (or an exception occurs), __exit__ is called — it receives exception info or None if success.
  5. If __exit__ returns True, the exception is suppressed (rarely done).

Here's the same file-reading example with with:

with open('server.log', 'r') as f:
    data = f.read()
# f is already closed here, even if an exception occurred

The file is closed before the next line of code. No manual cleanup needed.

Hands-on walkthrough

Basic file reading

# Write a sample file first
with open('example.txt', 'w') as f:
    f.write('Hello, context managers!')

# Read it back
with open('example.txt', 'r') as f:
    content = f.read()
    print(content)  # Output: Hello, context managers!

# Confirm file is closed: accessing f.name works, but I/O fails
# print(f.read())  # Uncommenting raises: ValueError: I/O operation on closed file.

Using multiple context managers

You can nest with statements or use a single with with commas (Python 3.1+):

# Nested (explicit)
with open('source.txt', 'r') as src:
    with open('dest.txt', 'w') as dst:
        dst.write(src.read())

# Single line (cleaner for multiple resources)
with open('source.txt', 'r') as src, open('dest.txt', 'w') as dst:
    dst.write(src.read())

Both forms ensure all resources are cleaned up. The second option is more readable.

Writing a custom context manager using a class

When you need to manage something other than files, create your own:

class ManagedResource:
    def __enter__(self):
        print('Acquiring resource')
        return self  # This becomes the 'as' variable

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is not None:
            print(f'Exception {exc_type.__name__}: {exc_val}')
        print('Releasing resource')
        return False  # Propagate the exception (default behavior)

with ManagedResource() as res:
    print('Using resource')
    # raise ValueError('Oops')  # Uncomment to see cleanup still runs

Output:

Acquiring resource
Using resource
Releasing resource

If an exception occurs, __exit__ still runs.

Using contextlib for simpler one-time managers

The contextlib module provides @contextmanager for generators (Python 3.6+):

from contextlib import contextmanager

@contextmanager
def managed_block(label):
    print(f'Entering {label}')
    yield
    print(f'Exiting {label}')

with managed_block('test'):
    print('Inside block')

Output:

Entering test
Inside block
Exiting test

The yield splits __enter__ (before yield) from __exit__ (after yield). Wrap the yield in a try/finally to handle errors inside the block.

Compare options / when to choose what

Approach Use case Example
with + built-in (file, lock) Standard resources with open(...) as f:
Custom class with __enter__/__exit__ Complex setup/teardown Database connection, network socket
@contextmanager decorator Simple manager without a class Quick resource wrapper
contextlib.closing() Legacy objects with .close() with closing(urllib.urlopen(...)): (rare)

When to choose: - For files, locks, and threading primitives, use with directly. - For custom resources (e.g., a timer, a database cursor), write a class or use @contextmanager. - For temporary files/directories, use tempfile.TemporaryFile() or pathlib with with.

Troubleshooting & edge cases

  • File not closed when with exits explicitly: If you call break, return, or continue inside the block, __exit__ still runs. That's by design.
  • Multiple context managers comma-separated: Order matters — each __exit__ is called in reverse order.
  • Custom manager doesn't handle exceptions: If __exit__ returns False (or None), the exception propagates. To suppress, return True. Use sparingly — it hides bugs.
  • __exit__ signature wrong: Must accept exactly (self, exc_type, exc_val, exc_tb). Missing args cause TypeError.
  • Generator-based context manger throws RuntimeError: If you yield more than once, Python raises an error. Always use yield exactly once.

Common error message:

AttributeError: __enter__

This means the object after with is not a context manager. Check that you're using a proper context manager (e.g., open() returns one).

What you learned & what's next

You now understand how with statements guarantee resource cleanup, how to use them with files, how to write custom class-based and generator-based context managers, and the key differences between approaches. You've mastered one of Python's most elegant resource management patterns.

Next step: Learn how context managers integrate with Python's exception handling in depth, or explore advanced contextlib utilities like ExitStack for dynamic context management.

Practice recap

Try this: Write a custom context manager that counts how many lines were read from a file. Use a class-based approach where __enter__ opens the file and __exit__ prints the line count. Then test it by reading a small file of your own — you'll see the power of automatic finalization.

Common mistakes

  • Forgetting to wrap custom resources in with — leaving files or locks open when exceptions occur.
  • Using a non-context-manager object with with, resulting in AttributeError: __enter__.
  • Defining __exit__ with wrong number of parameters — must accept (self, exc_type, exc_val, exc_tb).
  • Returning True from __exit__ without understanding that it suppresses all exceptions, hiding bugs.

Variations

  1. Use contextlib.closing() for objects with a .close() method but no __enter__/__exit__.
  2. Use contextlib.redirect_stdout() to temporarily redirect standard output inside a with block.
  3. Use contextlib.ExitStack when you need to manage a dynamic set of context managers at runtime.

Real-world use cases

  • Open a file for reading, process lines, and guarantee the file handle is released even on parse errors.
  • Wrap a shared database connection pool: acquire a connection on enter, return it on exit.
  • Automate temporary directory creation (with tempfile.TemporaryDirectory()) for unit tests, ensuring cleanup on failure.

Key takeaways

  • The with statement calls __enter__ on start and __exit__ on end, ensuring cleanup regardless of how the block exits.
  • Built-in objects like file handles, locks (from threading), and decimal.localcontext() are ready-to-use context managers.
  • Write a custom context manager by implementing __enter__ and __exit__ (class) or using the @contextmanager decorator with a generator.
  • When using multiple with managers, comma-separate them: with open('a', 'r') as f1, open('b', 'w') as f2:.
  • The contextlib module provides tools like closing() and ExitStack for advanced scenarios.
  • Always return False (or None) from __exit__ to propagate exceptions — suppress only when you have a good reason.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.