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
with open("note.txt", "w") as f:
f.write("ciao")
# qui f è già chiuso, anche se write() ha sollevato un'eccezioneEn 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
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__.
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 deas.__exit__(self, exc_type, exc_value, tb)se llama al salir (siempre). Si devuelveTrue, cualquier excepción es suprimida.
El atajo: @contextmanager
contextlib.contextmanager te permite escribir un gestor de contexto como una función generadora:
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
from contextlib import suppress
with suppress(FileNotFoundError):
open("forse-non-esiste.txt").read()
# se il file non c'è, nessun errore propagaPitfall: with solo para objetos que lo soportan
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:
from contextlib import contextmanager
@contextmanager
def manage_resource():
# __enter__ code
yield resource
# __exit__ codePruébalo tú
Usa contextlib.suppress para intentar int('non-numero') ignorando ValueError. Después del bloque asigna `ok = True`. Evalúa `ok`.
Mostrar pista
suppress(ValueError) traga solo ValueError.
Solución disponible después de 3 intentos
Ejercicio de repaso
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.
Mostrar pista
Usa yield items para exponer la lista dentro del bloque with.
Solución disponible después de 3 intentos
Desafío adicional
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():`.
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