New: 6 free SQL practice datasets with 300+ questions — try the SQL Compiler →
Python

Python Context Managers and the with Statement

Python context managers and the with statement explained: __enter__, __exit__, @contextmanager and suppress, with real code and output.

Upskly AI Team September 26, 2026 8 min read
Python Context Managers and the with Statement

A context manager in Python sets something up, gives it to you inside a with block, and guarantees the cleanup afterwards, even if an error happens. The most familiar one is with open("file.txt") as f:, which closes the file for you. You can also build your own with a class (__enter__ and __exit__) or with the @contextmanager decorator.

This guide explains why with exists, how it works under the hood, and how to write your own, with real output for every example.

In this guide

The short version

  • with thing as name: runs setup, runs your block, then always runs cleanup.
  • Cleanup happens even if the block raises an exception.
  • Build your own with __enter__ and __exit__, or with a generator and @contextmanager.
  • Use it for files, locks, database connections, temporary changes and timers.

The problem with (not) cleaning up

Some resources must be released after use: files should be closed, connections disconnected, locks released. Forgetting causes leaks and bugs. The safe manual way is a try with a finally, and it works, but it is long and easy to get wrong. The with statement does the same thing in fewer lines:

import io

buf = io.StringIO()
try:
    buf.write("data")
finally:
    buf.close()
print(buf.closed)

with io.StringIO() as buf2:
    buf2.write("data")
print(buf2.closed)

Output

True
True

Both versions ended with the buffer closed. If you are new to finally, see Python Exception Handling: try, except, finally.

The with statement

The most common use is with files. The file is open inside the block and closed the moment the block ends, so you never call close() yourself. The example below also uses a temporary folder, which is itself a context manager that deletes the folder when the block ends:

import os, tempfile

with tempfile.TemporaryDirectory() as folder:
    path = os.path.join(folder, "note.txt")
    with open(path, "w") as f:
        print(f.closed)
        f.write("hello")
    print(f.closed)
    with open(path) as f:
        print(f.read())
    inside = os.path.exists(folder)
print(inside, os.path.exists(folder))

Output

False
True
hello
True False

Follow the output: False (open inside the block), True (closed after it), the text we wrote, and finally the temporary folder existing inside its block and being gone afterwards. Reading and writing files is covered in Reading and Writing Files in Python.

The cleanup runs even when something goes wrong inside the block:

import io

try:
    with io.StringIO() as buf:
        buf.write("data")
        raise ValueError("oops")
except ValueError:
    print("caught")
print(buf.closed)

Output

caught
True

An error was raised inside the block, and the buffer was still closed before the exception reached the except.

How it works: __enter__ and __exit__

Any object with two special methods is a context manager. __enter__ runs at the start of the with block, and its return value is what as receives. __exit__ runs at the end, always, and is told whether an exception occurred:

class Timer:
    def __enter__(self):
        print("start")
        return self

    def __exit__(self, exc_type, exc, tb):
        print("stop", exc_type.__name__ if exc_type else "no error")
        return False

with Timer() as t:
    print("working")

try:
    with Timer():
        raise ValueError("bad")
except ValueError:
    print("error propagated")

Output

start
working
stop no error
start
stop ValueError
error propagated

In the second run, __exit__ received the ValueError, printed it, returned False, and the exception carried on to the outside world. This is what “guaranteed cleanup” means in practice.

The three parameters of __exit__ describe the exception (its type, value and traceback), and are all None when the block finished normally.

Suppressing exceptions

If __exit__ returns True, the exception is swallowed: the block stops, but the program continues after the with statement as if nothing happened:

class Ignore:
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc, tb):
        return True

with Ignore():
    1 / 0
print("continued")

Output

continued

Be careful with this: it hides errors. When you do want to ignore one specific, expected exception, the standard library has a neat tool, contextlib.suppress:

from contextlib import suppress

with suppress(KeyError):
    {}["missing"]
print("still running")

Output

still running

The easy way: @contextmanager

Writing a class for every context manager is heavy. contextlib.contextmanager lets you write a generator instead. Everything before yield is the setup, the yielded value is what as receives, and everything after is the cleanup:

from contextlib import contextmanager

@contextmanager
def announce(name):
    print("enter", name)
    try:
        yield name.upper()
    finally:
        print("exit", name)

with announce("db") as value:
    print("using", value)

Output

enter db
using DB
exit db

Wrap the yield in try / finally, as above. If you do not, an exception in the with block skips your cleanup code:

from contextlib import contextmanager

@contextmanager
def bad():
    print("setup")
    yield
    print("cleanup")

try:
    with bad():
        raise ValueError("boom")
except ValueError:
    print("error")

Output

setup
error

“cleanup” never printed. That is the most common bug when writing these. (Generators themselves are explained in Python Iterators and Generators Explained.)

Useful built-in context managers

Context managers you already have
Context managerWhat it does
open(path)Opens a file and closes it afterwards
tempfile.TemporaryDirectory()Makes a temporary folder and removes it afterwards
contextlib.suppress(Error)Ignores one expected kind of exception
contextlib.redirect_stdout(target)Sends print output somewhere else for a while
threading.Lock()Holds a lock for the length of the block
decimal.localcontext()Temporarily changes decimal precision
unittest.mock.patch(…)Temporarily replaces something while testing

For example, capturing what a piece of code prints:

import io
from contextlib import redirect_stdout

buf = io.StringIO()
with redirect_stdout(buf):
    print("captured")
print(repr(buf.getvalue()))

Output

'captured\n'

You can open several context managers in one with by separating them with commas:

import io

with io.StringIO("a\nb") as src, io.StringIO() as dst:
    for line in src:
        dst.write(line.strip().upper())
    print(dst.getvalue())

Output

AB

Common mistakes

Mistake 1: using the resource after the block

Once the block ends, the resource is closed and cannot be used any more:

import io

buf = io.StringIO()
buf.close()
try:
    buf.write("late")
except ValueError as e:
    print(type(e).__name__, e)

Output

ValueError I/O operation on closed file

Mistake 2: forgetting to return the object from __enter__

The value after as is whatever __enter__ returns. If you forget, you get None:

class Res:
    def __enter__(self):
        pass

    def __exit__(self, *args):
        pass

with Res() as r:
    print(r)

Output

None

Mistake 3: skipping try/finally inside @contextmanager

Shown above: without it the cleanup is skipped when the block fails.

Mistake 4: returning True from __exit__ by accident

That silently hides every exception from the block. Return True only when you deliberately mean to swallow one.

Try it yourself

Work out each answer first, then open the solution.

1. Write a context manager class that prints <tag> when the block starts and </tag> when it ends.

Show solution
class Tag:
    def __enter__(self):
        print("<tag>")

    def __exit__(self, *args):
        print("</tag>")

with Tag():
    print("content")

Output

<tag>
content
</tag>

2. What does this print?

from contextlib import suppress

with suppress(ZeroDivisionError):
    print("a")
    print(1 / 0)
    print("b")
print("c")
Show answer

Output

a
c

The error stops the block at the division, so “b” never prints. suppress swallows it, and the program continues with “c”.

3. Is the object closed after with io.StringIO() as f: pass?

Show answer
import io

with io.StringIO() as f:
    pass
print(f.closed)

Output

True

4. What does this print?

class C:
    def __enter__(self):
        print("in")
        return 5

    def __exit__(self, *args):
        print("out")

with C() as v:
    print(v + 1)
Show answer

Output

in
6
out

__enter__ printed “in” and returned 5, which became v. The block printed 6, then __exit__ printed “out”.

Try them in our free Python compiler.

Frequently asked questions

What is a context manager in Python?

It is an object that defines setup and cleanup around a block of code, used with the with statement. Files, locks and database connections are typical examples.

Why use with open() instead of open() and close()?

with closes the file automatically, even if an error occurs while you are using it. With a manual close(), an exception can skip it and leave the file open.

Does the with statement catch exceptions?

Not by itself. The exception still propagates after the cleanup runs, unless the context manager’s __exit__ returns True to suppress it.

How do I create my own context manager?

Either write a class with __enter__ and __exit__ methods, or write a generator function with a single yield and decorate it with @contextlib.contextmanager.

Can I use more than one context manager in a single with?

Yes. Separate them with commas: with open(a) as x, open(b) as y:. They are entered in order and exited in reverse order.

What is async with?

It is the asynchronous version, used with async code and objects that define __aenter__ and __aexit__. The idea is the same: guaranteed setup and cleanup.

Run this code in your browser

The free Upskly compiler runs Python with nothing to install. Paste the example, change it and see what happens.

Open Python Compiler

Stuck on a traceback?

AI Assist works inside the Python notebook, so you can ask about an error or a concept without leaving the cell.

Try AI Assist

Test yourself

Timed questions on Exceptions and Context Managers, with an explanation for every answer.

Take the Quiz
Upskly AI Team
Learning made simple
Scroll to Top