Aprende leyendo en orden

contextlib — Gestión de recursos y with personalizado

Aprende con ejemplos a escribir tu propio with sin clases con @contextmanager y yield, asegurar la limpieza con try / finally, y silenciar excepciones concretas con suppress.

contextlib es el módulo al que recurrir cuando quieres escribir tu propia sentencia with. Recorreremos sus dos herramientas más simples en orden: @contextmanager para la forma más simple de definir una, y suppress para silenciar excepciones específicas.

@contextmanager — define una sentencia with fácilmente

La sentencia with de Python es la forma segura de gestionar "cosas que tienes que limpiar después de usarlas"open de archivos, conexiones a BD, adquisición de bloqueos, etc. Para hacer que una clase funcione con with a mano, necesitas definir __enter__ / __exit__, que es mucho boilerplate. @contextmanager es un atajo: escribe un solo generador (una función que contiene yield) y obtienes un context manager que funciona con with. El código antes de yield es "el inicio de la acción", y el código después es "la limpieza".

Estructura de @contextmanager
@contextmanagerdef section(name):Setup(antes de yield)yieldTeardown(después de yield)
Una función generadora más @contextmanager — la parte antes de yield es el equivalente a __enter__ (setup), y la parte después de yield es el equivalente a __exit__ (teardown). El valor del yield es lo que se enlaza con as en la sentencia with.
from contextlib import contextmanager

# --- Referencia: construir una clase compatible con with ----------
# class Section:
#     def __init__(self, name):
#         self.name = name
#     def __enter__(self):
#         print(f"--- {self.name} inicio ---")
#         return self                 # valor enlazado por el `as` del with
#     def __exit__(self, exc_type, exc, tb):
#         print(f"--- {self.name} fin ---")
# ----------------------------------------------------------

# Versión @contextmanager: una función generadora hace el mismo trabajo
@contextmanager
def section(name):
    print(f"--- {name} inicio ---")
    yield                       # el cuerpo del with se ejecuta aquí
    print(f"--- {name} fin ---")

# Uso
with section("agregación"):
    total = sum(range(100))
    print("Total:", total)

# Salida:
# --- agregación inicio ---
# Total: 4950
# --- agregación fin ---
Versión clase → simplificación con @contextmanager
Versión clase__init__ / __enter__ /__exit__ — 3 métodosSimplifica con@contextmanagerVersión generadorantes de yield = setupdespués de yield = teardown
Reemplazar una clase con __init__ / __enter__ / __exit__ (tres métodos) por una sola función generadora decorada con @contextmanager mapea el código antes/después de yield directamente a setup/teardown — mucho menos código.

Usa try/finally para garantizar la limpieza incluso ante excepciones

El código después de yield puede no ejecutarse si se lanza una excepción dentro del bloque with. Para limpieza que siempre debe ocurrir — cerrar archivos, liberar bloqueos — envuelve el cuerpo del generador en try: yield finally: cleanup() por seguridad.

Construye una transaction(name) que modele una transacción de BD con @contextmanager. Imprime BEGIN antes y COMMIT después, con el SQL del bloque visible entre ambos.

① Importa contextmanager desde contextlib

② Define una función transaction(name) decorada con @contextmanager — imprime [name] BEGIN, luego yield, luego imprime [name] COMMIT después

③ Dentro de with transaction("crear_pedido"):, imprime dos líneas SQL: INSERT INTO orders (id, total) VALUES (1, 1980) y UPDATE inventory SET stock = stock - 1

(Si tu código se ejecuta correctamente, aparecerá la explicación.)

Editor Python

Ejecutar el código para ver el resultado

suppress — silencia excepciones específicas

contextlib.suppress(TipoExcepción, ...) es un context manager para silenciar las excepciones especificadas y continuar la ejecución. El patrón de "ignora solo esta excepción y sigue" — equivalente a try: ... except KeyError: pass — encaja en una sola línea. Puedes listar varios tipos de excepción a la vez, y cualquier cosa fuera de la lista se propaga normalmente.

from contextlib import suppress
import os

# "Ejecuta con valores por defecto incluso si el archivo no existe"
config = {"theme": "light", "font_size": 14}
with suppress(FileNotFoundError):
    with open("user_config.txt") as f:
        config["theme"] = f.read().strip()
# No falla si user_config.txt no existe — config conserva sus valores por defecto
print(config)
# → {'theme': 'light', 'font_size': 14}

# "Salta silenciosamente si ya fue eliminado"
with suppress(FileNotFoundError):
    os.remove("old.tmp")
print("Eliminación hecha (OK si no existía)")

Usa suppress para seguir adelante incluso al extraer claves faltantes de un dict.

① Importa suppress desde contextlib

② Define un dict user = {"name": "Ana", "email": "ana@example.com"} (nota: sin clave age)

③ Dentro de with suppress(KeyError):, intenta acceder a user["age"] — confirma que la clave faltante no falla

④ Justo después del bloque, imprime Continuando: nada falló

⑤ Imprime el nombre y el email del usuario en una sola línea como Usuario: ◯ <◯>

Editor Python

Ejecutar el código para ver el resultado
QUIZ

Verificación de conocimientos

Responde cada pregunta una a una.

Pregunta 1Cuando decoras una función generadora con @contextmanager, ¿por qué necesita hacer yield exactamente una vez?

Pregunta 2Dentro de with suppress(KeyError):, ¿qué pasa cuando se lanza KeyError?