DMarlon Studio

Lección 13 · 13 de 20

Excepciones propias y gestores de contexto · Python

Cree sus propias excepciones y gestores de contexto en Python: with, __enter__, __exit__, contextlib.contextmanager y suppress, con un almacén.

Python · Excepciones propias y gestores de contexto en DMarlon Studio: el código del ejemplo y su salida en la terminal

Una excepción propia es una clase que hereda de Exception: class StockError(Exception): pass basta. Permite capturar justo ese problema (falta de existencias) sin confundirlo con otros errores. take() la lanza con un mensaje que explica qué faltaba.

Un gestor de contexto es lo que va detrás de with. Transaction lo es porque tiene dos métodos: `__enter__` se ejecuta al entrar (guarda una copia del almacén y devuelve el diccionario, que llega a s gracias a as) y `__exit__` al salir, haya error o no.

Si dentro del bloque salta una excepción, __exit__ la recibe en exc_type y exc. Aquí restaura la copia con update(), de modo que los 3 bolígrafos retirados vuelven a su sitio: o se hace todo el pedido o nada. Devolver True le dice a Python que el error ya está tratado; por eso solo se silencia StockError y cualquier otro error seguiría su camino.

`@contextmanager` escribe un gestor con un generador: lo que va antes de yield es la entrada y lo que va después, la salida. section() escribe las etiquetas de inicio y fin. En un mismo with se pueden abrir varios gestores separados por comas. Al salir se cierran en orden inverso, primero Transaction y luego section: por eso la etiqueta de cierre sale después del aviso.

`suppress(KeyError)` es un gestor ya hecho que ignora un tipo concreto de error: pedir "paper", que no existe en el almacén, no detiene el programa.

Código · 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)

Salida · python3 main.py

[order 1]
  rolled back: ink: wanted 5, only 2 left
[/order 1]
Stock: {'pen': 10, 'ink': 2}

Para recordar

  • Una excepción propia hereda de Exception.
  • with llama a __enter__ al entrar y a __exit__ al salir, siempre.
  • Si __exit__ devuelve True, la excepción no se propaga.
  • @contextmanager crea un gestor con un generador y yield.
  • contextlib.suppress ignora los errores indicados.

Ejercicio

Haga un segundo pedido que sí se pueda servir (2 bolígrafos y 1 tinta) dentro de section("order 2") y compruebe que esta vez el almacén cambia.