Lesson 13 · 13 of 20
Custom exceptions and context managers · Python
Create your own exceptions and context managers in Python: with, __enter__, __exit__, contextlib.contextmanager and suppress, in a warehouse.

A custom exception is a class that inherits from Exception: class StockError(Exception): pass is enough. It lets you catch exactly that problem (running out of stock) without mixing it up with other errors. take() raises it with a message saying what was missing.
A context manager is whatever goes after with. Transaction is one because it has two methods: `__enter__` runs on the way in (it saves a copy of the stock and returns the dictionary, which reaches s through as) and `__exit__` runs on the way out, error or not.
If an exception is raised inside the block, __exit__ receives it in exc_type and exc. Here it restores the copy with update(), so the 3 pens already taken go back: either the whole order goes through or none of it does. Returning True tells Python the error has been dealt with; that is why only StockError is silenced and any other error would carry on up.
`@contextmanager` builds a manager from a generator: the code before yield is the way in and the code after it is the way out. section() prints the opening and closing tags. A single with can open several managers separated by commas. They close in reverse order on the way out, first Transaction and then section: that is why the closing tag comes after the rollback message.
`suppress(KeyError)` is a ready-made manager that ignores one kind of error: asking for "paper", which is not in stock, does not stop the program.
Code · main.py
from contextlib import contextmanager, suppressclass StockError(Exception): pass # our own kind of errorclass Transaction: # a context manager written as a class def __init__(self, stock): self.stock = stock def __enter__(self): self.backup = dict(self.stock) return self.stock def __exit__(self, exc_type, exc, tb): if exc_type is not None: # something failed: undo everything self.stock.update(self.backup) print(f" rolled back: {exc}") return exc_type is StockError # True = error handled here@contextmanager # the same idea with a generatordef section(title): print(f"[{title}]") yield print(f"[/{title}]")def take(stock, product, qty): if stock[product] < qty: raise StockError(f"{product}: wanted {qty}, only {stock[product]} left") stock[product] -= qtystock = {"pen": 10, "ink": 2}with section("order 1"), Transaction(stock) as s: take(s, "pen", 3) take(s, "ink", 5) # fails: the pens come back toowith suppress(KeyError): # ignore one specific error take(stock, "paper", 1)print("Stock:", stock)Output · python3 main.py
[order 1]
rolled back: ink: wanted 5, only 2 left
[/order 1]
Stock: {'pen': 10, 'ink': 2}Key points
- A custom exception inherits from
Exception. withcalls__enter__on entry and__exit__on exit, always.- If
__exit__returnsTrue, the exception does not propagate. @contextmanagerbuilds a manager from a generator withyield.contextlib.suppressignores the errors you name.
Exercise
Place a second order that can be fulfilled (2 pens and 1 ink) inside section("order 2") and check that the stock changes this time.