La sentencia que limpia después de sí misma
Cada recurso que abres en un programa — un archivo, una conexión de red, un handle de base de datos, un lock — necesita cerrarse cuando termines. Olvídalo y filtras memoria, mantienes locks que bloquean a otros procesos o corrompes archivos en un crash. La sentencia with de Python se encarga de esto por ti.
El patrón con el que todo el mundo empieza es leer un archivo:
with open("notes.txt") as f:
contents = f.read()
print(contents)
Pasan dos cosas automáticamente. Al entrar, open() te da un objeto archivo atado a f. Al salir — ya sea que el bloque termine normalmente, haga un return temprano o lance — Python llama a f.close() por ti.
Y ya está. Ese es el punto entero de with.
Qué reemplaza "With"
Antes de los context managers, el código seguro equivalente era un try/finally:
f = open("notes.txt")
try:
contents = f.read()
print(contents)
finally:
f.close()
Cinco líneas de ceremonia para "leer un archivo y cerrarlo cuando termine". Multiplica eso por cada open en un programa más grande y empiezas a ver el atractivo. with es más corto, más difícil de equivocar e imposible olvidarse de la limpieza.
Abrir varios recursos
Puedes atar varios context managers en un solo with:
with open("input.txt") as src, open("output.txt", "w") as dst:
dst.write(src.read().upper())
Ambos archivos se abren al entrar, ambos se cierran al salir. Si el primer open tiene éxito y el segundo lanza, Python aún cierra el primero — la maquinaria maneja el setup parcial correctamente.
Para listas más largas de recursos, la forma con paréntesis (Python 3.10+) es más clara:
with (
open("a.txt") as a,
open("b.txt") as b,
open("c.txt") as c,
):
...
Qué es realmente un context manager
Cualquier objeto que define __enter__ y __exit__ es un context manager. El protocolo es muy simple:
__enter__(self)se ejecuta cuando el bloquewithempieza. Su valor de retorno es a lo que se ataas name.__exit__(self, exc_type, exc_value, traceback)se ejecuta cuando el bloque termina, independientemente de cómo. Si una excepción causó la salida, la información de la excepción se pasa para que el context manager pueda inspeccionarla o suprimirla.
Aquí hay uno mínimo que cronometra el bloque que envuelve:
with Timer(): crea el objeto, llama a su __enter__, ejecuta el cuerpo, llama a __exit__. Sin archivo, sin lock — solo un pequeño envoltorio alrededor de "haz algo, mide cuánto tardó".
El atajo contextlib.contextmanager
Definir una clase para cada context manager es más pesado de lo que hace falta. contextlib.contextmanager convierte una función generadora en un context manager — un yield separa el "antes" del "después":
Todo antes del yield es el comportamiento de __enter__. Todo después es el de __exit__. El try/finally hace que la limpieza se ejecute incluso si el cuerpo lanza.
La mayoría de context managers personalizados que escribas encajan en esta forma. Recurre primero a la forma con decorador; baja a una clase solo cuando necesites algo que la forma con generador no puede expresar.
Cambiar algo temporalmente
Una forma común: pon algo, úsalo, restáuralo. Los context managers expresan esto limpiamente:
Cualquier patrón "pon, luego restaura" — variables de entorno, verbosidad de logging, feature flags, fixtures de tests — encaja naturalmente en un context manager. Quien llama no tiene que acordarse de restaurar nada.
Suprimir excepciones
El método __exit__ puede devolver True para decirle a Python "he manejado la excepción; trágatela". Eso es raro y suele ser una señal de alarma, pero es como funciona contextlib.suppress:
suppress(FileNotFoundError) convierte el FileNotFoundError en un no-op. Úsalo para operaciones genuinamente opcionales — "prueba esto, no me importa si no funciona". No lo uses para silenciar excepciones que no has pensado.
Otros context managers que te encontrarás
Los context managers aparecen por toda la librería estándar cuando empiezas a mirar:
import threading
from pathlib import Path
# Locks — garantizan liberación incluso si la sección crítica lanza.
lock = threading.Lock()
with lock:
...
# tempfile — borra el archivo temporal cuando termines.
from tempfile import TemporaryDirectory
with TemporaryDirectory() as tmp:
path = Path(tmp) / "scratch.txt"
path.write_text("hello")
# Conexiones de base de datos — cierra la conexión (o termina la transacción).
import sqlite3
with sqlite3.connect(":memory:") as conn:
conn.execute("CREATE TABLE t (x INTEGER)")
Las librerías de terceros siguen las mismas convenciones. Cuando ves with something as x:, casi siempre significa "usa x durante la duración de este bloque y limpia después".
Cuándo no usar with
- Cuando realmente no tienes setup ni teardown. Envolver código arbitrario en un context manager sin razón añade ruido.
- Cuando necesitas el recurso a través de muchos bloques no relacionados. Mantener un
withabierto durante toda la vida de un script largo puede ocultar cuál es realmente el alcance de la limpieza. Considera una clase que sea dueña del recurso en su lugar. - Cuando un decorador encaja mejor. Algunos patrones repetidos (retry, log, time) se leen más naturalmente como
@decoratoren una función que comowith ...:dentro. Elige el que se lea mejor en el sitio de la llamada.
La mayor parte del tiempo, with está bien. Las raras excepciones son fáciles de detectar cuando las buscas.
Siguiente: trabajar con archivos reales
Ahora conoces el mecanismo detrás de with open(...) as f: — que es el contexto en el que lo usarás el noventa por ciento del tiempo. El siguiente capítulo lo pone a trabajar leyendo, escribiendo y navegando archivos en disco.
Preguntas frecuentes
¿Qué hace with open en Python?
with open en Python?with open(path) as f: abre el archivo y lo ata a f durante la duración del bloque. Cuando el bloque termina — normalmente o por una excepción — Python cierra el archivo automáticamente. No necesitas f.close(); la sentencia with lo garantiza.
¿Por qué usar with en vez de open() a secas?
with en vez de open() a secas?Porque with cierra el archivo incluso cuando una excepción dispara a mitad del bloque. open() a secas te deja a ti recordar close() en cada camino del código, incluidos los caminos de error. with es más seguro y más corto.
¿Cómo abro varios archivos con una sola sentencia with?
with?Separa con comas los context managers: with open('a.txt') as a, open('b.txt') as b:. Ambos archivos se abren al entrar y se cierran al salir, en orden inverso. Esto reemplaza sentencias with anidadas cuando necesitas varios recursos a la vez.