1.3.4 · D3Python Intermediate

Worked examples — Context managers — with statement, `__enter__` - `__exit__`

3,833 words17 min readBack to topic

Every example below is a cell in one master table. Read the table first — it is the whole topic on one screen.


The scenario matrix

The behaviour of a with block is decided by exactly two questions:

  1. Did the body raise an exception? (yes / no)
  2. What did __exit__ return? (truthy / falsy — where "falsy" includes None, the default)

That is a 2×2 grid. But there are also degenerate and edge cases that live outside the body: what if __enter__ itself raises? What if __exit__ raises? What about nesting, and the @contextmanager generator form? Here is the full matrix.

Cell Body raised? __exit__ returned What the reader sees Example
A No (irrelevant) Cleanup runs, program continues Ex 1
B Yes falsy (None/False) Cleanup runs, error propagates Ex 2
C Yes truthy (True) Cleanup runs, error swallowed Ex 3
D Yes truthy only for matching types, else falsy Selective swallow Ex 4
E — degenerate __enter__ raises __exit__ never runs Setup failed, no cleanup Ex 5
F — degenerate Body ok, __exit__ raises New error replaces old Ex 6
G — limiting return/break inside body Still "normal exit" → cleanup runs Ex 7
H — word problem Real bank-transfer rollback Truthy vs falsy decides commit Ex 8
I — exam twist @contextmanager + exception at yield Exception is thrown into the generator Ex 9
J — nesting Two managers, inner raises Cleanup runs inner-first (LIFO) Ex 10

How to read the figure below. It is a picture of the core 2×2 — the two questions on the two axes. The rows answer "did the body raise?": the top row is NO, the bottom row is YES. The columns answer "what did __exit__ return?": the left column is falsy (None/False), the right column is truthy (True). Each coloured tile is the outcome the reader sees for that combination:

  • Top row (orange, Cell A): no exception, so the return value is ignored either way — cleanup runs and the program continues.
  • Bottom-left (magenta, Cell B): an exception plus a falsy return → cleanup runs and the error propagates.
  • Bottom-right (violet, Cell C): an exception plus a truthy return → cleanup runs and the error is swallowed.

The edge cells E, F, G, J are printed as a note along the bottom of the figure, because they act outside the body and therefore cannot sit inside this 2×2 grid. (If the image fails to load, that three-bullet description is the figure.)

Figure — Context managers — with statement, `__enter__`  -  `__exit__`

Every example names its cell. Guess the output before reading the steps.


Ex 1 — Cell A: clean exit, no exception


Ex 2 — Cell B: body raises, __exit__ returns falsy → propagate


Ex 3 — Cell C: body raises, __exit__ returns True → swallow


Ex 4 — Cell D: swallow only the right exception type


Ex 5 — Cell E (degenerate): __enter__ raises → __exit__ never runs


Ex 6 — Cell F (degenerate): __exit__ itself raises


Ex 7 — Cell G (limiting): return / break inside the block


Ex 8 — Cell H (word problem): a transaction that commits or rolls back


Ex 9 — Cell I (exam twist): @contextmanager and the exception thrown into yield


Ex 10 — Cell J (nesting): cleanup order is LIFO

Figure — Context managers — with statement, `__enter__`  -  `__exit__`

Recall

Body raised, __exit__ returned None — what happens?
Cleanup runs, then the exception propagates (Cell B). None is falsy.
Body raised, __exit__ returned True — what happens?
Cleanup runs, exception swallowed (Cell C).
__enter__ raises — does __exit__ run?
No (Cell E). Entry never completed, so there is nothing to clean up.
__exit__ itself raises — which error propagates?
The new one from __exit__ (Cell F); the original is attached as __context__.
A return inside the block — does __exit__ run, and what is et?
Yes it runs; et is None because return is not an exception (Cell G).
In with A, B:, order of __exit__ calls?
Inner first, then outer — LIFO (Cell J).
In a @contextmanager, how does a body exception reach the generator?
It is thrown back in at the yield via gen.throw (Cell I).
What do et, ev, tb stand for?
exception type (class), exception value (object), and traceback — all None on a clean exit.

Connections

  • Exception handling — try-except-finally — every cell here is a try/finally with a return-value twist.
  • Generators and yield — powers the throw-into-yield behaviour of Ex 9.
  • Decorators@contextmanager is the decorator wrapping Ex 9's generator.
  • File I/O — Ex 5's "acquire last" rule is why open never leaks half-open files.
  • Threading and locks — Ex 10's LIFO teardown is the lock-release order.
  • Dunder methods__enter__/__exit__ are the protocol under test throughout.