Skip to content
Foxipa

Suggest an edit

Tell us what should be corrected, clarified, or improved. Submission will be connected to Foxipa’s contribution service later.

No information is sent anywhere yet. Your suggestion is only prepared in your browser.

Learning

Python With Statements and Context Managers

Learn Python context managers, entry and cleanup, exception propagation, multiple resources, and contextlib.suppress(), nullcontext(), and closing().

Levels: Intermediate ~24 min read

Intermediate

A context manager controls a runtime context around a block of code. Python's with statement enters that context before the block and exits it afterward, making setup and cleanup easier to keep together.

Start with with

The with statement takes a context manager and runs a block inside its context. The optional as target receives the value returned by __enter__().

python
class Session:
    def __enter__(self):
        print("start")
        return "ready"

    def __exit__(self, exc_type, exc_value, traceback):
        print("finish")

with Session() as state:
    print(state)

The object after with is the context manager. The name after as is not assigned that object automatically; it receives the value returned by __enter__().

__enter__ prepares the context

A context manager's __enter__ method runs before the with block. It can establish the runtime context and return a useful value for the as target.

python
class Counter:
    def __enter__(self):
        self.count = 0
        print("entered")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print("exiting")

with Counter() as counter:
    counter.count += 1
    print(counter.count)

__exit__ always handles normal block exit

If __enter__ completes successfully, Python calls __exit__ when the with block finishes. When the block ends normally, the exception arguments passed to __exit__ are all None.

python
class TimerLabel:
    def __enter__(self):
        print("begin")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print("end", exc_type is None)

with TimerLabel():
    print("work")

__exit__ also runs when the block raises

The cleanup part is the important guarantee: after __enter__ succeeds, __exit__ is called even when the with block raises an exception. If __exit__ returns a false value, the exception continues propagating.

python
class Cleanup:
    def __enter__(self):
        print("open")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print("cleanup", exc_type.__name__)
        return False

try:
    with Cleanup():
        raise ValueError("bad data")
except ValueError as error:
    print("handled", error)

An __exit__ method can suppress an exception

If __exit__ returns a true value after an exception, the with statement suppresses that exception and execution continues after the with statement. This should be used deliberately because it changes normal error propagation.

python
class IgnoreValueError:
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        return exc_type is ValueError

with IgnoreValueError():
    raise ValueError("ignored")

print("continues")

Suppress a specific expected exception with contextlib.suppress

The standard-library contextlib.suppress() helper can replace a small try/except block when one specific exception is an expected and harmless outcome. It suppresses only the exception types listed in the call; other exception types still propagate. Unlike the previous example, you do not need to write a custom __exit__ method just to express this policy.

Interactive Runnable example
Edit

Ready to run.

Quick Check: keep exception suppression narrow

The program should ignore a missing dictionary key but let unrelated errors propagate. Which context manager matches that intent?

from contextlib import suppress

settings = {"theme": "dark"}
with suppress(KeyError):
    settings.pop("language")
print("theme:", settings["theme"])

Keep the with block small. suppress(KeyError) handles a KeyError raised anywhere inside that block, not only the line you intended to guard. Do not use suppress(Exception) or suppress(BaseException) as a general-purpose error-handling strategy; catch and report failures that are not known to be harmless.

Use nullcontext when a resource is already managed elsewhere

contextlib.nullcontext(value) gives you a context manager that returns value from __enter__() and otherwise does nothing. It is useful when one function accepts either a path it should open and close itself or an already-open stream whose caller keeps ownership. In the path case, use open() as the context manager; in the borrowed-stream case, use nullcontext(stream). The with statement then has one consistent shape without accidentally closing the caller’s stream.

Interactive Runnable example
Edit

Ready to run.

Quick Check: preserve the caller’s resource

Which expression binds active to the existing stream without closing it when the with block ends?

from contextlib import nullcontext
from io import StringIO

stream = StringIO("caller-owned\n")
with  as active:
    print(type(active).__name__)
    print("same object:", active is stream)
print("stream closed:", stream.closed)

nullcontext() is not a cleanup helper: it does not call close() or suppress exceptions. It simply provides the same with/as structure when no context-manager work is needed. Use it when ownership should remain with the caller, and use a real resource context manager when the current function owns the resource.

Close objects that do not implement the context-manager protocol

Some legacy or third-party objects provide close() but do not implement __enter__() and __exit__(), so they cannot be used directly in a with statement. contextlib.closing(object) adapts such an object: it returns the object from __enter__() and calls its close() method when the block exits, including when the block raises an exception. Prefer an object’s native context-manager support when it has one; use closing() when close() is its cleanup interface and this scope is responsible for closing it.

Interactive Runnable example
Edit

Ready to run.

Quick Check: close a legacy resource

A resource has close() but does not implement __enter__() or __exit__(). Which expression ensures that this resource is closed when the with block exits?

from contextlib import closing, nullcontext

class LegacyReader:
    def __init__(self):
        self.closed = False
    def close(self):
        self.closed = True

reader = LegacyReader()
with  as active:
    print("same object:", active is reader)
print("reader closed:", reader.closed)

Unlike nullcontext(), closing() really does close the object. Unlike suppress(), it does not handle or hide an exception from the block: cleanup runs and the exception still propagates. Do not use it to close a resource that belongs to the caller; ownership and cleanup responsibility must remain clear.

Use more than one context manager

A with statement can manage multiple context managers. Python processes them like nested with statements, so the later context is entered inside the earlier one and exits in the reverse order.

python
class Mark:
    def __init__(self, name):
        self.name = name

    def __enter__(self):
        print("enter", self.name)

    def __exit__(self, exc_type, exc_value, traceback):
        print("exit", self.name)

with Mark("A"), Mark("B"):
    print("body")

with is a cleanup tool, not just a file feature

File objects are one familiar example, but with is a general language feature. Any object that implements the context-manager protocol can define setup and cleanup behavior. The File I/O lesson teaches how to use with for files; this lesson explains the language mechanism behind it.

python
class Connection:
    def __enter__(self):
        print("connect")
        return self

    def send(self, message):
        print("send:", message)

    def __exit__(self, exc_type, exc_value, traceback):
        print("disconnect")

with Connection() as connection:
    connection.send("hello")

Key takeaways

  • with runs a block inside a context manager's runtime context.
  • __enter__ runs before the block and supplies the value used by as, when present.
  • __exit__ runs after a successfully entered context, including when the block raises an exception.
  • A false __exit__ result lets an exception propagate; a true result suppresses it.
  • Multiple context managers behave like nested with statements and exit in reverse order.
Was this page helpful?

Mark this learning resource complete to track your learning progress.