Python
context managers
with statement
programming
code duplication

Create a with block on several context managers?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Python allows multiple context managers in a single with statement. That is usually the cleanest answer when you need several resources to be acquired together, such as files, locks, database transactions, or temporary directories.

Basic Syntax

The simplest form is a comma-separated list of context managers.

python
with open("input.txt", "r", encoding="utf-8") as src, \
     open("output.txt", "w", encoding="utf-8") as dst:
    dst.write(src.read())

This is equivalent to nested with blocks, but it is flatter and easier to read.

python
with open("input.txt", "r", encoding="utf-8") as src:
    with open("output.txt", "w", encoding="utf-8") as dst:
        dst.write(src.read())

Both forms call the __enter__ methods from left to right and the __exit__ methods in reverse order.

Why the Exit Order Matters

Reverse cleanup order is important when resources depend on one another. Imagine acquiring a lock and then opening a file used inside the protected section.

python
1from threading import Lock
2
3lock = Lock()
4
5with lock, open("audit.log", "a", encoding="utf-8") as log_file:
6    log_file.write("critical section\n")

Python enters lock first and open(...) second. On exit, the file closes before the lock releases. That matches the usual expectation that inner resources are cleaned up before outer coordination objects disappear.

Aliases Are Optional Per Manager

Not every context manager needs an as target. In the previous example, the lock does not produce a value that we need to bind, but the file does.

This also works with more than two managers.

python
1from decimal import localcontext, Decimal
2
3with localcontext() as ctx, open("values.txt", "w", encoding="utf-8") as f:
4    ctx.prec = 4
5    result = Decimal("1") / Decimal("7")
6    f.write(str(result))

Each manager behaves independently; Python just combines the setup and teardown logic into one readable statement.

When the Number of Managers Is Dynamic

A plain with statement is best when you know the managers at code-writing time. If the number is dynamic, use contextlib.ExitStack.

python
1from contextlib import ExitStack
2
3paths = ["a.txt", "b.txt", "c.txt"]
4
5with ExitStack() as stack:
6    files = [stack.enter_context(open(path, "w", encoding="utf-8"))
7             for path in paths]
8
9    for index, f in enumerate(files, start=1):
10        f.write(f"file {index}\n")

ExitStack gives you the same cleanup guarantee, but lets you add managers programmatically. That is the right tool when files, sockets, or transactions are discovered at runtime.

Exception Behavior

If one of the later context managers fails during entry, Python will still exit the managers that were already entered successfully. That is another reason to prefer a real with statement over manual try and finally code.

python
1class Demo:
2    def __init__(self, name, fail=False):
3        self.name = name
4        self.fail = fail
5
6    def __enter__(self):
7        print(f"enter {self.name}")
8        if self.fail:
9            raise RuntimeError("entry failed")
10        return self
11
12    def __exit__(self, exc_type, exc, tb):
13        print(f"exit {self.name}")
14
15
16try:
17    with Demo("first"), Demo("second", fail=True):
18        pass
19except RuntimeError:
20    print("handled")

In that example, first is still exited even though second fails during __enter__.

Style Guidance

Use a single combined with when the resources conceptually belong to one operation. If the setup logic is complicated or each resource needs its own comments, nested blocks can still be clearer.

The goal is not to compress everything into one line. The goal is to make acquisition and cleanup rules obvious.

Common Pitfalls

A common mistake is trying to build a dynamic number of managers with a plain comma-separated with. That only works for a fixed set known ahead of time. Use ExitStack when the list is built at runtime.

Another mistake is misunderstanding exit order. Cleanup happens in reverse order, so if resource ordering matters, put the outermost resource first.

Also avoid replacing context managers with manual close() calls unless you have a strong reason. It is easy to miss exception paths and leak resources.

Summary

  • Python supports multiple context managers in one with statement.
  • Entry happens from left to right, and exit happens in reverse order.
  • A combined with is equivalent to nested with blocks, but often easier to read.
  • Use contextlib.ExitStack when the number of managers is dynamic.
  • Prefer real context management over manual cleanup code.

Course illustration
Course illustration

All Rights Reserved.