La sentencia with en Python administra recursos que deben abrirse y cerrarse correctamente. Su uso más conocido es trabajar con archivos, pero también se aplica a conexiones, bloqueos, transacciones y objetos temporales que implementan el protocolo de context manager.
El principal beneficio es que la limpieza ocurre incluso cuando se produce una excepción. Esto reduce archivos abiertos, bloqueos olvidados y código repetido dentro de bloques try y finally.
Abrir un archivo sin with
file = open("notes.txt", encoding="utf-8")
content = file.read()
file.close()Si read() genera un error, close() puede no ejecutarse. Una versión segura necesitaría:
file = open("notes.txt", encoding="utf-8")
try:
content = file.read()
finally:
file.close()with expresa este patrón de forma más clara.
Leer un archivo con with
with open("notes.txt", encoding="utf-8") as file:
content = file.read()
print(content)Cuando termina el bloque indentado, Python cierra el archivo. La variable puede seguir existiendo, pero el recurso ya no admite operaciones.
La referencia oficial de la sentencia with explica su comportamiento y equivalencia con try/finally.
Modos de apertura
r: lectura.w: escritura, reemplazando el archivo.a: añadir al final.x: crear y fallar si ya existe.rbywb: modos binarios.+: lectura y escritura.
La guía de archivos de texto en Python explica lectura, escritura, append y codificación.
Escribir texto
with open(
"report.txt",
"w",
encoding="utf-8",
) as file:
file.write("Informe mensual\n")
file.write("Estado: completado\n")El modo w borra el contenido anterior. Para conservarlo, utiliza a:
with open(
"activity.log",
"a",
encoding="utf-8",
) as file:
file.write("Nueva ejecución\n")Utilizar print() con un archivo
with open(
"summary.txt",
"w",
encoding="utf-8",
) as file:
print("Resumen", file=file)
print("Total: 120", file=file)print() añade saltos de línea y admite sep y end. La guía de print() en Python presenta más opciones.
Leer línea por línea
Para archivos grandes, no cargues todo con read():
with open("events.log", encoding="utf-8") as file:
for line_number, line in enumerate(file, start=1):
cleaned = line.rstrip()
print(line_number, cleaned)El archivo funciona como iterable y entrega una línea cada vez. Este patrón utiliza poca memoria.
Leer una línea o todas
with open("notes.txt", encoding="utf-8") as file:
first_line = file.readline()
remaining = file.readlines()readlines() crea una lista con todas las líneas restantes. Para archivos grandes, la iteración suele ser mejor.
Trabajar con pathlib
from pathlib import Path
path = Path("data") / "report.txt"
with path.open("w", encoding="utf-8") as file:
file.write("Contenido seguro\n")La guía de pathlib en Python explica cómo construir rutas portables y recorrer carpetas.
Archivos CSV
import csv
with open(
"sales.csv",
encoding="utf-8",
newline="",
) as file:
reader = csv.DictReader(file)
for row in reader:
print(row["product"], row["amount"])El argumento newline="" permite que el módulo CSV gestione correctamente los saltos. La guía de CSV en Python explica lectores, escritores y delimitadores.
Abrir varios archivos a la vez
with (
open("source.txt", encoding="utf-8") as source,
open("destination.txt", "w", encoding="utf-8") as destination,
):
for line in source:
destination.write(line.upper())Ambos recursos se cierran al finalizar. Si la segunda apertura falla, el primer recurso también se libera.
Manejar errores
from pathlib import Path
path = Path("config.json")
try:
with path.open(encoding="utf-8") as file:
content = file.read()
except FileNotFoundError:
print("El archivo no existe")
except PermissionError:
print("No hay permisos para leerlo")with garantiza la limpieza, pero no oculta el error. La guía de try y except en Python explica cómo capturar excepciones específicas.
Qué es un context manager
Un context manager implementa dos métodos:
__enter__(): prepara el recurso y devuelve el valor asignado después deas.__exit__(): ejecuta la limpieza al salir.
class ManagedResource:
def __enter__(self):
print("Abriendo recurso")
return self
def __exit__(self, exc_type, exc_value, traceback):
print("Cerrando recurso")
return False
with ManagedResource() as resource:
print("Utilizando recurso")Devolver False permite que una excepción continúe. Devolver True la suprime, lo que debe hacerse solamente de forma deliberada.
Crear context managers con contextlib
from contextlib import contextmanager
from time import perf_counter
@contextmanager
def timer(label):
start = perf_counter()
try:
yield
finally:
elapsed = perf_counter() - start
print(f"{label}: {elapsed:.4f} segundos")
with timer("Procesamiento"):
total = sum(range(1_000_000))El código anterior a yield prepara el contexto. El bloque finally garantiza la limpieza.
La documentación oficial de contextlib incluye contextmanager, closing, suppress y ExitStack.
Bloqueos con with
from threading import Lock
lock = Lock()
with lock:
update_shared_data()El bloqueo se libera incluso si la función genera una excepción.
Conexiones y transacciones
import sqlite3
with sqlite3.connect("app.db") as connection:
connection.execute(
"INSERT INTO events(message) VALUES (?)",
("Aplicación iniciada",),
)El comportamiento exacto de commit, rollback y cierre depende de la biblioteca. Consulta su documentación; no supongas que todos los objetos hacen lo mismo.
Archivos temporales
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as folder:
path = Path(folder) / "report.txt"
path.write_text(
"datos temporales",
encoding="utf-8",
)
print(path.read_text(encoding="utf-8"))Al salir, el directorio temporal se elimina. Este patrón es útil en pruebas y conversiones.
ExitStack para recursos dinámicos
Cuando la cantidad de archivos no se conoce de antemano:
from contextlib import ExitStack
paths = ["a.txt", "b.txt", "c.txt"]
with ExitStack() as stack:
files = [
stack.enter_context(
open(path, encoding="utf-8")
)
for path in paths
]
contents = [file.read() for file in files]ExitStack registra cada recurso y los cierra en orden inverso.
Ejemplo: copiar y transformar registros
from pathlib import Path
source_path = Path("raw.txt")
target_path = Path("clean.txt")
with (
source_path.open(encoding="utf-8") as source,
target_path.open("w", encoding="utf-8") as target,
):
for line in source:
cleaned = " ".join(line.strip().split())
if not cleaned:
continue
target.write(cleaned + "\n")Este ejemplo procesa línea por línea, normaliza espacios y mantiene ambos archivos bajo administración segura.
Errores frecuentes
- Abrir texto sin especificar UTF-8.
- Usar
wcuando se quería añadir. - Intentar utilizar el archivo fuera del bloque.
- Capturar excepciones demasiado amplias.
- Suponer que todo context manager cierra una conexión.
- Suprimir errores devolviendo
Truesin necesidad. - Cargar archivos enormes con
read().
With frente a try/finally
with no reemplaza conceptualmente a try/finally; lo encapsula mediante un protocolo reutilizable. Utiliza with cuando el objeto ofrezca un context manager. Crea uno propio cuando exista una preparación y una limpieza que deban permanecer siempre juntas.
Conclusión
La sentencia with hace explícito el ciclo de vida de un recurso. Utilízala para archivos, bloqueos, conexiones y objetos temporales. Domina los modos de apertura, la codificación, el manejo de excepciones y el protocolo __enter__/__exit__. El resultado será código más corto, seguro y fácil de revisar.






