{}const=>[]async()letfn</>var
Desarrollo

Gestores de contexto en Python: with, enter y exit

Analizamos el operador with, los métodos enter y exit, creamos nuestros propios gestores contextuales y estudiamos ejemplos prácticos de uso para trabajar de forma segura con los recursos.

К

Kodik

Autor

6 min de lectura

Imagina una situación: abres un archivo, trabajas con él y luego te olvidas de cerrarlo. O abres una conexión a la base de datos y de repente surge una excepción que interrumpe la ejecución del código antes de que puedas liberar los recursos. ¿Te suena? Los gestores de contexto en Python resuelven este problema de forma elegante y fiable.

¿Por qué necesitamos gestores contextuales?

Los gestores de contexto proporcionan una gestión automática de los recursos. Garantizan que un código específico se ejecute antes de comenzar a trabajar con el recurso y después de su finalización, independientemente de si se produjo un error o no.

Ejemplo clásico sin gestor de contexto:

file = open('data.txt', 'r')
try:
    content = file.read()
    # Trabajamos con el contenido
finally:
    file.close()  # ¡No olvides cerrar!

Con el gestor contextual todo es mucho más fácil:

with open('data.txt', 'r') as file:
    content = file.read()
    # El archivo se cerrará automáticamente después de salir del bloque

🔥 100.000+ estudiantes ya están con nosotros

¿Cansado de leer teoría?
¡Hora de programar!

Kodik — una app donde aprendes a programar con práctica. Mentor IA, lecciones interactivas, proyectos reales.

🤖 IA 24/7
🎓 Certificados
💰 Gratis
🚀 Empezar
Se unieron hoy

¿Cómo funciona la construcción with?

Cuando Python se encuentra con el operador with, sucede lo siguiente:

  1. Se llama al método __enter__() del objeto

  2. El valor devuelto __enter__() se asigna a la variable después de as (si se especifica)

  3. El código se ejecuta dentro del bloque with

  4. Después de completar el bloque (o cuando se produce una excepción), se llama al método __exit ()

Esto garantiza que la limpieza de los recursos se produzca siempre, incluso si se produce un error dentro del bloque.

Creamos nuestro propio gestor contextual

Para crear tu propio gestor contextual, debes implementar dos métodos especiales: __enter__ y __exit__.

Un ejemplo sencillo: el administrador de conexión a la base de datos

class DatabaseConnection:
    def __init__(self, db_name):
        self.db_name = db_name
        self.connection = None
    
    def __enter__(self):
        print(f"Opening connection to {self.db_name}")
        self.connection = f"Connection to {self.db_name}"
        return self.connection
    
    def __exit__(self, exc_type, exc_value, traceback):
        print(f"Closing connection to {self.db_name}")
        if exc_type is not None:
            print(f"An error occurred: {exc_type.__name__}: {exc_value}")
        return False  # La excepción se pasará más adelante

# Uso
with DatabaseConnection("users_db") as conn:
    print(f"We work with: {conn}")
    # Aquí está tu código de trabajo con la base de datos

Conclusión:

Открываем соединение с users_db
Работаем с: Connection to users_db
Закрываем соединение с users_db

Método __enter ()

El método __enter__() se llama al entrar en el contexto. Puede:

  • Inicializar recursos

  • Establecer conexiones

  • Capturar bloqueos

  • Devolver el objeto para el trabajo (que estará disponible a través de as)

def __enter__(self):
    # Preparación de recursos
    self.resource = self.acquire_resource()
    return self.resource  # Este valor se asignará a la variable después de 'as'

Importante: el método puede devolver cualquier objeto, incluido self. Si no necesita un valor de retorno, puede simplemente usar with sin as.

Método __exit__(exc_type, exc_value, traceback)

El método __exit__() se llama al salir del contexto y toma tres argumentos:

  • exc_type — tipo de excepción (o None, si no hubo excepción)

  • exc_value — objeto de excepción

  • traceback — objeto traceback para la excepción

El valor devuelto determina si se suprime la excepción:

  • True: la excepción se suprime (no se propaga más)

  • False o None: la excepción se propaga más allá

Ejemplo con manejo de excepciones

class ErrorHandler:
    def __init__(self, suppress_errors=False):
        self.suppress_errors = suppress_errors
    
    def __enter__(self):
        print("Starting execution")
        return self
    
    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type is not None:
            print(f"Caught exception: {exc_type.__name__}")
            if self.suppress_errors:
                print("Exception suppressed")
                return True  # Suprimir la excepción
        print("Completing")
        return False  # Lanzando una excepción

# Se suprimirá la excepción
with ErrorHandler(suppress_errors=True):
    print("Executing code")
    raise ValueError("Something went wrong!")
    print("This line will not be executed")

print("The program continues to work")

Ejemplos prácticos de uso

Temporizador de ejecución de código

import time

class Timer:
    def __enter__(self):
        self.start = time.time()
        return self
    
    def __exit__(self, *args):
        self.end = time.time()
        self.elapsed = self.end - self.start
        print(f"Execution time: {self.elapsed:.4f} seconds")

with Timer():
    # Medimos el tiempo de ejecución
    total = sum(range(1000000))

Cambio temporal de directorio

import os

class ChangeDirectory:
    def __init__(self, new_path):
        self.new_path = new_path
        self.saved_path = None
    
    def __enter__(self):
        self.saved_path = os.getcwd()
        os.chdir(self.new_path)
        return self
    
    def __exit__(self, *args):
        os.chdir(self.saved_path)

with ChangeDirectory('/tmp'):
    print(f"Current directory: {os.getcwd()}")
    # Trabajamos en /tmp

print(f"Returned to: {os.getcwd()}")

Gestión de transacciones

class Transaction:
    def __init__(self, connection):
        self.connection = connection
    
    def __enter__(self):
        self.connection.begin()
        return self.connection
    
    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type is None:
            self.connection.commit()
            print("Transaction confirmed")
        else:
            self.connection.rollback()
            print("Transaction rolled back")
        return False

# Uso
# with Transaction(db_connection) as conn:
#     conn.execute("INSERT INTO users VALUES (...)")
# # Si se produce un error, los cambios se revertirán

Módulo contextlib: gestores contextuales sin clases

Python proporciona el módulo contextlib, que permite crear gestores de contexto más fácilmente utilizando el decorador @contextmanager:

from contextlib import contextmanager

@contextmanager
def managed_file(filename, mode):
    print(f"Opening file {filename}")
    file = open(filename, mode)
    try:
        yield file  # Todo hasta yield es __enter__
    finally:
        print(f"Closing file {filename}")
        file.close()  # Todo después de yield es __exit__

with managed_file('test.txt', 'w') as f:
    f.write('Hello, world!')

La palabra clave yield separa la lógica: el código anterior se ejecuta al entrar en el contexto, el código posterior al salir.

Supresión de excepciones con contextlib.suppress

from contextlib import suppress

# Ignorar FileNotFoundError
with suppress(FileNotFoundError):
    os.remove('non-existent_file.txt')

print("The program continues to work")

Múltiples gestores contextuales

Python permite usar varios gestores de contexto en un solo operador with:

with open('input.txt', 'r') as infile, open('output.txt', 'w') as outfile:
    content = infile.read()
    outfile.write(content.upper())

Esto es equivalente a los bloques anidados:

with open('input.txt', 'r') as infile:
    with open('output.txt', 'w') as outfile:
        content = infile.read()
        outfile.write(content.upper())

¿Cuándo usar los gestores contextuales?

Los gestores contextuales son ideales para situaciones en las que se necesita una operación de "ajuste/limpieza" en pareja:

  • Trabajar con archivos

  • Gestión de conexiones con la base de datos

  • Bloqueos en aplicaciones multiproceso

  • Cambio temporal del estado del sistema

  • Gestión de transacciones

  • Medición del tiempo de ejecución

  • Registro de entrada y salida de bloques de código

Consejos y mejores prácticas

Siempre libera recursos en __exit__(). Incluso si se produce una excepción, se llamará al método __exit__(), por lo que debe limpiar correctamente los recursos.

Utiliza contextlib para casos simples. Si no necesita todo el poder de la clase, el decorador @contextmanager simplificará enormemente el código.

Tenga cuidado con la supresión de excepciones. Devuelve True desde __exit__() solo cuando estés seguro de que la excepción se ha manejado correctamente.

Los gestores de contexto se pueden reutilizar. El mismo objeto del gestor de contexto se puede utilizar varias veces si reinicializa correctamente el estado.

Prueba el comportamiento de las excepciones. Asegúrate de que tu gestor de contexto funciona correctamente tanto en modo normal como cuando se producen errores.

Los gestores de contexto hacen que tu código sea más limpio, seguro y fiable. Eliminan la necesidad de recordar la limpieza manual de los recursos y garantizan que todo se haga correctamente, incluso si algo sale mal. Este es un ejemplo de cómo Python ayuda a los desarrolladores a escribir un código mejor con menos esfuerzo.

Anexo Kodik ofrece cursos estructurados para desarrolladores principiantes en Python, JavaScript, HTML, CSS y otras tecnologías. La formación está estructurada de tal manera que puedas dominar gradualmente el material de simple a complejo.

Únete a nuestro Canal de Telegram, donde encontrarás el apoyo de la comunidad, podrás hacer preguntas y obtener ayuda para aprender programación. ¡Aprender juntos es más fácil y más interesante!

🎯Deja de postergar

¿Te gustó el artículo?
¡Hora de practicar!

En Kodik no solo lees — escribes código de inmediato. Teoría + práctica = habilidades reales.

Práctica instantánea
🧠IA explica código
🏆Certificado

Sin registro • Sin tarjeta