contextvars en Python: contexto asíncrono

Publicado el: 11/08/2026
Tempo de leitura: 5 minutos
Flujo de datos en red que representa contexto asíncrono con contextvars en Python

El módulo contextvars mantiene valores locales al contexto de ejecución. Resuelve un problema común en aplicaciones concurrentes: disponer de ID de solicitud, usuario, tenant, locale o metadatos de tracing sin pasar argumentos por todas las funciones y sin permitir que el estado de una tarea se filtre a otra.

En código síncrono con threads, threading.local() puede servir. En asyncio, muchas tareas comparten la misma thread y alternan su ejecución, por lo que ContextVar sigue a la tarea lógica.

Declarar una ContextVar

Crea variables a nivel de módulo, no dentro de closures. Los objetos Context mantienen referencias fuertes y una creación dinámica puede impedir la recolección.

from contextvars import ContextVar

request_id: ContextVar[str] = ContextVar("request_id")
usuario_actual: ContextVar[str | None] = ContextVar(
    "usuario_actual", default=None
)

El nombre sirve para depuración. El default opcional debe ser coherente con el tipo esperado.

Leer el valor actual

get() busca en el contexto actual. Devuelve primero el default del método, después el definido al crear la variable y, si no existe ninguno, genera LookupError.

print(usuario_actual.get())

try:
    print(request_id.get())
except LookupError:
    print("request_id no definido")

Para metadatos obligatorios, omitir el default puede revelar errores de integración. Para datos realmente opcionales, un default explícito simplifica el uso.

Definir y restaurar con token

set() cambia el valor en el contexto actual y devuelve un Token que restaura exactamente el estado anterior.

token = request_id.set("req-123")
try:
    procesar()
finally:
    request_id.reset(token)

El mismo token no puede usarse dos veces y pertenece a la variable que lo creó.

Tokens como context managers en Python 3.14

Python 3.14 permite usar el token devuelto por set() como context manager.

with request_id.set("req-456"):
    print(request_id.get())

# valor anterior restaurado

Para compatibilidad con versiones anteriores, usa set(), try/finally y reset().

Aislamiento en asyncio

Cuando se crea una task, se copia su contexto actual. Los cambios posteriores dentro de una tarea quedan aislados.

import asyncio
from contextvars import ContextVar

nombre = ContextVar("nombre")

async def trabajo(valor):
    with nombre.set(valor):
        await asyncio.sleep(0.01)
        return nombre.get()

async def main():
    resultado = await asyncio.gather(trabajo("A"), trabajo("B"))
    print(resultado)

asyncio.run(main())

Incluso después de un await, cada tarea lee su propio valor.

No es una global normal

El objeto ContextVar es global, pero el valor depende del contexto. Copiarlo también a una variable global común destruye el aislamiento.

No uses contexto para ocultar datos de dominio que deberían ser argumentos. Es más apropiado para metadatos pequeños y transversales.

ID de solicitud en logs

request_id = ContextVar("request_id", default="-")

def log(mensaje):
    print(f"[{request_id.get()}] {mensaje}")

async def tratar_solicitud(identificador):
    with request_id.set(identificador):
        log("inicio")
        await llamar_servicio()
        log("fin")

Los frameworks de logging pueden usar filtros o adapters que consulten la variable. Evita añadir secretos o datos personales innecesarios.

Tenant y usuario

Una aplicación multitenant puede exponer el tenant actual por contexto, pero cada consulta debe aplicar filtros y autorización. La propagación contextual no es una barrera de seguridad.

Define el valor en el borde de la solicitud, valida el principal y restáuralo al finalizar.

copy_context

copy_context() copia el contexto actual en tiempo O(1), independientemente del número de variables.

from contextvars import copy_context

ctx = copy_context()
for variable, valor in ctx.items():
    print(variable.name, valor)

La copia puede ejecutar código con ctx.run(funcion, *args). Los cambios permanecen en ese objeto Context, no en el contexto exterior.

Ejecutar en un contexto específico

ctx = copy_context()

def tarea():
    request_id.set("aislado")
    return request_id.get()

resultado = ctx.run(tarea)

El mismo contexto no puede entrar simultáneamente más de una vez, incluso desde otra thread. Esto genera RuntimeError. Tras salir, puede reutilizarse.

Propagación a threads

Cada thread tiene su propia pila efectiva de contextos. La propagación a un executor depende de la API. Para un comportamiento explícito, captura el contexto y ejecuta dentro de él.

ctx = copy_context()
futuro = executor.submit(ctx.run, funcion)

No envíes el mismo contexto de forma concurrente a varios workers. Crea copias separadas.

ContextVar frente a threading.local

threading.local() aísla por thread física. Cientos de tareas asyncio pueden vivir en una sola thread, por lo que compartirían el estado local. ContextVar sigue el contexto lógico.

Defaults mutables

Evita defaults mutables compartidos, como listas y diccionarios. El aislamiento se aplica a la referencia, no a mutaciones del mismo objeto.

# evitar
errores = ContextVar("errores", default=[])

# crear un valor por ámbito
with errores.set([]):
    errores.get().append("fallo")

Valores inmutables, IDs y dataclasses congeladas reducen sorpresas.

Tokens anidados y orden

Las definiciones pueden anidarse. Restaura en orden inverso.

t1 = request_id.set("externo")
t2 = request_id.set("interno")
request_id.reset(t2)
request_id.reset(t1)

Usar el token equivocado o reutilizarlo genera error. La sintaxis de context manager de Python 3.14 hace el anidamiento más visible.

Callbacks y planificación

Los callbacks suelen ejecutarse en el contexto capturado por la API que los planificó, pero un framework puede definir reglas distintas. Prueba con el event loop, executors y callbacks reales.

Probar aislamiento

async def probar_aislamiento():
    async def leer(valor):
        with request_id.set(valor):
            await asyncio.sleep(0)
            return request_id.get()

    a, b = await asyncio.gather(leer("A"), leer("B"))
    assert (a, b) == ("A", "B")

Prueba también excepciones, cancelación, subtasks y ejecución en thread. Confirma que el valor anterior se restaura.

No crear un contenedor oculto

Guardar clientes de base de datos, HTTP y servicios completos en contexto oculta dependencias y complica tests. Pasa dependencias principales explícitamente y reserva el contexto para metadatos pequeños.

Errores frecuentes

  • Crear ContextVars dentro de closures.
  • Olvidar restaurar el token.
  • Usar un default mutable compartido.
  • Tratar el contexto como autorización.
  • Esperar que thread-local aísle tareas asyncio.
  • Entrar simultáneamente en el mismo Context.
  • Ocultar dependencias importantes en contexto.

Buenas prácticas

  • Declara ContextVars a nivel de módulo.
  • Usa nombres útiles.
  • Restaura valores con tokens o context managers.
  • Prefiere datos pequeños e inmutables.
  • Captura contexto explícitamente al cambiar de thread.
  • Prueba cancelación y concurrencia.
  • Mantén autorización fuera del contexto.

Guías relacionadas

Continúa con ExitStack en Python, inspect en Python, faulthandler en Python, traceback en Python y operator en Python.

Consulta la documentación oficial de contextvars y la PEP 567.

Conclusión

contextvars ofrece estado contextual aislado para código síncrono y asíncrono. Es ideal para IDs de solicitud, tracing, locale y pequeños metadatos transversales. Su uso seguro exige ámbitos claros, restauración garantizada, defaults inmutables y pruebas entre tareas y threads.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Código de programación que representa operaciones como funciones con operator en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    operator en Python: operaciones como funciones

    Aprende operator en Python para usar operaciones como funciones, ordenar campos, acceder a elementos, llamar métodos y crear pipelines claros.

    Ler mais

    Tempo de leitura: 4 minutos
    11/08/2026
    Alfabeto tridimensional que representa normalización Unicode con unicodedata en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    unicodedata en Python: normaliza Unicode

    Aprende unicodedata en Python para normalizar Unicode, consultar nombres, categorías, números, marcas combinantes y ancho de visualización.

    Ler mais

    Tempo de leitura: 5 minutos
    11/08/2026
    Red de servidores que representa la gestión de recursos con ExitStack en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    ExitStack en Python: gestiona recursos

    Aprende ExitStack en Python para gestionar archivos, conexiones, callbacks y limpieza dinámica con seguridad y orden predecible.

    Ler mais

    Tempo de leitura: 6 minutos
    11/08/2026
    Carpeta con candado que representa tipos y permisos con stat en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    stat en Python: tipos y permisos

    Aprende stat en Python para interpretar tipos de archivo, permisos, enlaces, timestamps, atributos de Windows y flags de Unix con

    Ler mais

    Tempo de leitura: 5 minutos
    10/08/2026
    Portátil con código que representa documentación automática con pydoc en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    pydoc en Python: documentación automática

    Aprende pydoc en Python para generar ayuda en terminal, HTML, búsqueda y un servidor local de documentación de forma segura.

    Ler mais

    Tempo de leitura: 6 minutos
    10/08/2026
    Teclado internacional que representa números, moneda y fechas con locale en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    locale en Python: números, moneda y fechas

    Aprende locale en Python para formatear e interpretar números, moneda, fechas, encodings y orden cultural sin errores de concurrencia.

    Ler mais

    Tempo de leitura: 6 minutos
    09/08/2026