Saltar al contenido principal
eLearner.app
Módulo 9 · Lección 3 de 435/36 en el curso~12 min
Lecciones del módulo (3/4)

Context managers: with y __enter__/__exit__

Un gestor de contexto (context manager) es un objeto que se usa con with. Garantiza que ciertas operaciones de configuración y limpieza ocurran siempre, incluso si se producen errores. El ejemplo canónico: abrir y cerrar un archivo.

El patrón with

Python
with open("note.txt", "w") as f:
    f.write("ciao")
# qui f è già chiuso, anche se write() ha sollevato un'eccezione

En lenguaje sencillo: "con el recurso X abierto como f, haz esto. Al terminar (pase lo que pase), cierra X". Sin with, tendrías que escribir un try/finally cada vez.

Más de uno a la vez

Python
with open("a.txt") as f, open("b.txt") as g:
    dati_a = f.read()
    dati_b = g.read()

Escribir tu propio gestor de contexto (clase)

Necesitas definir dos métodos especiales (dunder): __enter__ y __exit__.

Python
class Timer:
    def __enter__(self):
        import time
        self.t0 = time.perf_counter()
        return self  # è l'oggetto che finisce dopo "as"
    def __exit__(self, exc_type, exc_value, traceback):
        import time
        self.elapsed = time.perf_counter() - self.t0
        # ritornare True ingoia l'eccezione; di solito non lo vuoi
        return False

with Timer() as t:
    sum(range(10**6))
t.elapsed  # quanto è durato
  • __enter__(self) se llama al entrar en el bloque; el valor devuelto es lo que obtienes después de as.
  • __exit__(self, exc_type, exc_value, tb) se llama al salir (siempre). Si devuelve True, cualquier excepción es suprimida.

El atajo: @contextmanager

contextlib.contextmanager te permite escribir un gestor de contexto como una función generadora:

Python
from contextlib import contextmanager

@contextmanager
def timer():
    import time
    t0 = time.perf_counter()
    try:
        yield  # qui sta il blocco "with"
    finally:
        elapsed = time.perf_counter() - t0
        print(f"durata: {elapsed:.4f}s")

with timer():
    sum(range(10**6))

El código antes de yield es __enter__, el código después es __exit__. El bloque try/finally garantiza la limpieza incluso si ocurren errores.

suppress: ignorar una excepción

Python
from contextlib import suppress

with suppress(FileNotFoundError):
    open("forse-non-esiste.txt").read()
# se il file non c'è, nessun errore propaga

Pitfall: with solo para objetos que lo soportan

Python
x = 42
with x:  # AttributeError: __enter__
    ...

Escribir gestores de contexto con @contextmanager

El módulo contextlib de la biblioteca estándar exporta un decorador muy conveniente llamado @contextmanager para crear gestores de contexto usando funciones generadoras, en lugar de escribir clases verbosas que implementen los métodos __enter__ y __exit__. Simplemente escribes una función generadora que usa un único yield:

Python
from contextlib import contextmanager

@contextmanager
def manage_resource():
    # __enter__ code
    yield resource
    # __exit__ code

Pruébalo tú

Ejercicio#python.m9.l3.e1
Intentos: 0Cargando...

Usa contextlib.suppress para intentar int('non-numero') ignorando ValueError. Después del bloque asigna `ok = True`. Evalúa `ok`.

Cargando editor...
Mostrar pista

suppress(ValueError) traga solo ValueError.

Solución disponible después de 3 intentos

Ejercicio de repaso

Ejercicio#python.m9.l3.e2
Intentos: 0Cargando...

Escribe un gestor de contexto `collect` con @contextmanager que devuelva una lista vacía y, al salir, la convierta en tupla (imprímela). Úsalo para añadir 1, 2, 3 a la lista dentro del bloque. Al final, evalúa `len(collection)` donde `collection` es la lista devuelta.

Cargando editor...
Mostrar pista

Usa yield items para exponer la lista dentro del bloque with.

Solución disponible después de 3 intentos

Desafío adicional

Ejercicio#python.m9.l3.e3
Intentos: 0Cargando...

Escribe un gestor de contexto personalizado usando el decorador `@contextmanager` de `contextlib`. El gestor de contexto debe llamarse `mock_session`. Al entrar, debe añadir `'start'` a una lista global `log_list = []`, ceder el control usando `yield`, y al salir añadir `'stop'`. Finalmente, evalúa `log_list` después de usar el gestor de contexto con `with mock_session():`.

Cargando editor...
Mostrar pista

Usa yield dentro de mock_session para suspender la ejecución antes de añadir 'stop'.

Solución disponible después de 3 intentos