No todos los flujos necesitan abrir un archivo, iniciar una transacción o adquirir un lock. Aun así, muchas funciones son más fáciles de mantener cuando la lógica principal siempre se ejecuta dentro de un bloque with. contextlib.nullcontext() resuelve este diseño mediante un context manager que no realiza acciones especiales al entrar o salir y simplemente devuelve el valor recibido.
Es útil para APIs que aceptan un objeto ya abierto o una fuente que debe abrirse, pruebas que alternan entre un contexto real y uno neutro, código síncrono y asíncrono y funciones que habilitan transacciones, tracing o locks solamente cuando una opción está activa.
Qué hace nullcontext
nullcontext es un context manager neutro. Al entrar devuelve el valor pasado como enter_result. Al salir no suprime excepciones ni ejecuta limpieza adicional.
from contextlib import nullcontext
with nullcontext("listo") as valor:
print(valor)
Su ventaja principal aparece cuando se selecciona entre un contexto real y el contexto neutro, permitiendo mantener un único bloque de procesamiento.
Contexto opcional sin duplicar lógica
from contextlib import nullcontext
from pathlib import Path
def leer_fuente(origen):
contexto = open(origen, encoding="utf-8") if isinstance(origen, Path) else nullcontext(origen)
with contexto as archivo:
return archivo.read()
Si origen es una ruta, la función abre y cierra el archivo. Si es un stream existente, nullcontext solo lo entrega. La lógica de lectura permanece en un único lugar.
Ownership del recurso
El patrón expresa una regla importante: el componente que crea un recurso normalmente debe cerrarlo. Una función que recibe un stream ya abierto no debería cerrarlo porque el llamador continúa siendo su propietario. nullcontext representa esta diferencia sin duplicar el procesamiento.
Documenta el contrato. Una API ambigua puede cerrar recursos prestados o dejar abiertos los propios. Usa nombres claros, ejemplos y pruebas para ambas formas aceptadas.
Locks opcionales
from contextlib import nullcontext
from threading import Lock
lock = Lock()
def actualizar(cache, clave, valor, sincronizado=True):
contexto = lock if sincronizado else nullcontext()
with contexto:
cache[clave] = valor
El cuerpo es idéntico en ambos modos. Este patrón puede servir en componentes single-thread y multi-thread. No permitas desactivar la sincronización cuando sea necesaria para la corrección de los datos.
Transacciones opcionales
def guardar(sentencias, conexion, transaccional=True):
contexto = conexion.begin() if transaccional else nullcontext()
with contexto:
for sentencia in sentencias:
conexion.execute(sentencia)
La interfaz exacta depende de la biblioteca. Comprueba si el contexto real hace commit, rollback o cierre y confirma que su semántica sea compatible con el camino neutro.
Devolver un valor con enter_result
cliente_existente = crear_cliente()
with nullcontext(cliente_existente) as cliente:
cliente.enviar()
enter_result permite que el contexto neutro tenga la misma forma que un contexto real que entrega el recurso mediante as.
Uso asíncrono
Las versiones modernas de Python también permiten usar nullcontext con async with. Una coroutine puede utilizar una sesión asíncrona existente o crear una nueva.
from contextlib import nullcontext
async def obtener(url, sesion=None):
contexto = crear_sesion() if sesion is None else nullcontext(sesion)
async with contexto as cliente:
return await cliente.get(url)
El contexto real debe implementar el protocolo asíncrono. Comprueba además si la factory devuelve directamente un async context manager o una coroutine que debe esperarse antes.
Factories para aclarar propiedad
Recibir una factory en vez de un objeto opcional puede comunicar mejor el ownership. La función crea el recurso mediante la factory y asume su ciclo de vida.
def procesar(factory=None):
contexto = factory() if factory else nullcontext(recurso_predeterminado)
with contexto as recurso:
ejecutar(recurso)
Las factories también mejoran las pruebas porque una implementación falsa puede registrar adquisición y liberación.
Combinación con ExitStack
Cuando varios contextos son opcionales, ExitStack evita condiciones profundamente anidadas.
from contextlib import ExitStack, nullcontext
with ExitStack() as stack:
archivo = stack.enter_context(open(ruta)) if ruta else stack.enter_context(nullcontext(None))
stack.enter_context(lock if usar_lock else nullcontext())
ejecutar(archivo)
Para una cantidad dinámica de recursos, ExitStack suele ser la solución más escalable.
Las excepciones no se suprimen
with nullcontext():
raise ValueError("fallo")
La excepción se propaga normalmente. nullcontext no equivale a contextlib.suppress. El contexto neutro conserva el comportamiento del bloque y no oculta errores.
nullcontext frente a suppress
nullcontext no realiza ninguna acción al salir. suppress captura tipos concretos de excepción. Resuelven problemas distintos y no deben intercambiarse solo porque ambos pertenecen a contextlib.
nullcontext frente a un manager personalizado
Crea un context manager propio cuando debas registrar métricas, validar estado, transformar excepciones, ejecutar callbacks o liberar recursos. Usa nullcontext cuando el comportamiento realmente deba ser neutro.
Tipado de contextos opcionales
Las funciones públicas pueden describir context managers mediante ContextManager[T], AsyncContextManager[T] o un Protocol. Normaliza valores directos y contextos en la frontera de la función.
Estrategia de pruebas
- Verifica que
enter_resultllegue al objetivo deas. - Confirma que las excepciones se propaguen.
- Prueba recursos propios y prestados.
- Asegura que solo se cierren los recursos creados internamente.
- En async, prueba cancelación y fallos del contexto real.
Errores comunes
- Cerrar un recurso prestado: conserva la diferencia de ownership.
- Usar suppress en su lugar: puede ocultar fallos.
- Asumir soporte async en cualquier versión: revisa la versión mínima.
- Adquirir recursos demasiado pronto: usa una factory cuando necesites lazy acquisition.
- Mezclar modelos de propiedad: indica quién crea y cierra cada objeto.
Diseño recomendado
Selecciona el contexto al inicio de la función, mantén un único cuerpo principal y nombra los parámetros para que el ownership sea evidente. Para varios recursos dinámicos usa ExitStack o AsyncExitStack. También puedes comparar este patrón con la guía interna de contextlib.aclosing.
Conclusión
contextlib.nullcontext es una utilidad pequeña con un beneficio arquitectónico importante. Elimina ramas duplicadas en flujos con archivos, locks, transacciones, sesiones y recursos ya existentes, manteniendo intactas las excepciones y las fronteras de propiedad.







