O módulo contextvars mantém valores locais ao contexto de execução. Ele resolve um problema comum em aplicações concorrentes: disponibilizar informações como ID da requisição, usuário atual, tenant, locale ou metadados de tracing sem passar argumentos por todas as funções e sem permitir que o estado de uma tarefa vaze para outra.
Em código síncrono com threads, threading.local() pode atender alguns casos. Em asyncio, várias tarefas compartilham a mesma thread e alternam a execução. Variáveis de contexto acompanham a tarefa lógica e são propagadas pelas APIs assíncronas do Python.
Criar uma ContextVar
Crie variáveis no nível do módulo, não dentro de closures. Objetos Context mantêm referências fortes às variáveis, e criá-las dinamicamente pode impedir coleta de lixo.
from contextvars import ContextVar
request_id: ContextVar[str] = ContextVar("request_id")
usuario_atual: ContextVar[str | None] = ContextVar(
"usuario_atual", default=None
)O nome serve para depuração e introspecção. O parâmetro default é opcional e deve ser coerente com o tipo esperado.
Ler um valor
get() procura o valor no contexto atual. A prioridade é: padrão passado ao método, padrão definido na criação e, se nenhum existir, LookupError.
print(usuario_atual.get())
try:
print(request_id.get())
except LookupError:
print("request_id ainda não definido")Para dados obrigatórios, não definir um default pode revelar erros de integração mais cedo. Para dados realmente opcionais, um valor padrão explícito simplifica o uso.
Definir e restaurar com token
set() altera o valor no contexto atual e devolve um Token. O token restaura exatamente o estado anterior.
token = request_id.set("req-123")
try:
processar()
finally:
request_id.reset(token)Use try/finally para garantir restauração mesmo quando ocorre uma exceção. O mesmo token não pode ser usado duas vezes e pertence à variável que o criou.
Token como context manager no Python 3.14
Desde Python 3.14, o token retornado por set() implementa o protocolo de context manager.
with request_id.set("req-456"):
print(request_id.get())
# valor anterior restaurado automaticamenteEsse formato aproxima a definição temporária do escopo em que ela vale. Para código compatível com versões anteriores, mantenha set(), try/finally e reset().
Isolamento em asyncio
Quando uma task é criada, o contexto atual é copiado para ela. Alterações posteriores dentro de uma tarefa ficam isoladas das demais.
import asyncio
from contextvars import ContextVar
nome = ContextVar("nome")
async def trabalho(valor):
with nome.set(valor):
await asyncio.sleep(0.01)
return nome.get()
async def main():
resultados = await asyncio.gather(
trabalho("A"),
trabalho("B"),
)
print(resultados)
asyncio.run(main())Mesmo com alternância após o await, cada tarefa lê seu próprio valor.
ContextVar não é variável global comum
O objeto ContextVar é global, mas seu valor depende do contexto. Guardar o valor também em uma variável global comum destrói o isolamento.
Evite usar contexto para dados de domínio que deveriam ser argumentos explícitos. Ele é mais adequado a metadados transversais usados por muitas camadas.
Request ID em logs
request_id = ContextVar("request_id", default="-")
def log(mensagem):
print(f"[{request_id.get()}] {mensagem}")
async def tratar_requisicao(id_requisicao):
with request_id.set(id_requisicao):
log("início")
await chamar_servico()
log("fim")Bibliotecas de logging podem usar filtros ou adaptadores que consultam a variável. Não coloque segredos ou dados pessoais desnecessários em logs.
Estado de tenant ou usuário
Aplicações multitenant podem disponibilizar o tenant atual por contexto, mas toda consulta ao banco ainda deve aplicar filtros e autorização. A variável facilita propagação; ela não cria uma barreira de segurança.
Defina o valor na borda da requisição, valide o usuário e restaure no final. Teste explicitamente tarefas criadas antes e depois da definição.
copy_context
copy_context() copia o contexto atual com complexidade O(1), independentemente da quantidade de variáveis.
from contextvars import copy_context
ctx = copy_context()
for var, valor in ctx.items():
print(var.name, valor)A cópia pode ser executada em outro ponto com ctx.run(funcao, *args). Alterações feitas dentro dela ficam no objeto Context, não no contexto externo.
Executar código em um contexto específico
ctx = copy_context()
def tarefa():
request_id.set("isolado")
return request_id.get()
resultado = ctx.run(tarefa)Não é permitido entrar simultaneamente no mesmo Context mais de uma vez, inclusive a partir de outra thread. Isso gera RuntimeError. Depois de sair, ele pode ser reutilizado.
Propagação para threads
Cada thread possui sua própria pilha efetiva de contextos. Ao enviar trabalho para um executor, a propagação pode depender da API e da versão. Quando precisar de comportamento explícito, capture com copy_context() e execute com ctx.run().
ctx = copy_context()
futuro = executor.submit(ctx.run, funcao)Não compartilhe o mesmo contexto simultaneamente entre vários trabalhos. Crie cópias separadas.
Diferença para threading.local
threading.local() isola por thread física. Em um event loop, centenas de tasks podem viver na mesma thread, então todas enxergariam o mesmo armazenamento local à thread. ContextVar acompanha o contexto lógico e funciona com asyncio.
Em bibliotecas que podem ser usadas em código concorrente, variáveis de contexto normalmente são a opção correta para estado contextual.
Defaults mutáveis
Evite defaults mutáveis compartilhados, como listas e dicionários. A variável isola a referência, mas mutações no mesmo objeto continuam visíveis.
# evite
erros = ContextVar("erros", default=[])
# prefira criar um valor por escopo
with erros.set([]):
erros.get().append("falha")Objetos imutáveis, IDs e dataclasses congeladas reduzem surpresas.
Tokens e ordem de restauração
Definições podem ser aninhadas. Restaure em ordem inversa, como uma pilha.
t1 = request_id.set("externo")
t2 = request_id.set("interno")
request_id.reset(t2)
request_id.reset(t1)Usar o token errado ou reutilizá-lo gera erro. Context managers do Python 3.14 tornam a ordem mais visível.
Contexto em callbacks e tarefas
Callbacks normalmente executam no contexto capturado pela API que os agendou. Ainda assim, frameworks podem definir regras próprias. Integrações devem ser testadas com tarefas, executores, threads e callbacks reais.
Evite assumir que um objeto criado em uma tarefa sempre executará no mesmo contexto se uma biblioteca transfere trabalho para outro runtime.
Testar isolamento
async def teste_isolamento():
async def ler(valor):
with request_id.set(valor):
await asyncio.sleep(0)
return request_id.get()
a, b = await asyncio.gather(ler("A"), ler("B"))
assert (a, b) == ("A", "B")Teste também exceções, cancelamento, criação de subtasks e execução em thread. Após cada teste, confirme que o valor anterior foi restaurado.
Não usar como contêiner de dependências
Colocar banco, cliente HTTP e serviços inteiros no contexto esconde dependências e dificulta testes. Passe dependências principais explicitamente. Reserve ContextVar para informações contextuais pequenas e transversais.
Erros frequentes
- Criar ContextVars dentro de closures.
- Esquecer de restaurar o token.
- Usar default mutável compartilhado.
- Tratar contexto como autorização.
- Esperar que
threading.local()isole tasks asyncio. - Compartilhar o mesmo Context simultaneamente.
- Esconder dependências importantes no contexto.
Boas práticas
- Declare ContextVars no nível do módulo.
- Use nomes úteis para depuração.
- Restaure valores com token ou context manager.
- Prefira dados pequenos e imutáveis.
- Capture contexto explicitamente ao mudar de thread.
- Teste cancelamento e concorrência.
- Preserve autorização e validação fora do contexto.
Conteúdos relacionados
Veja ExitStack, inspect, faulthandler, traceback e operator.
Consulte a documentação oficial do contextvars e a PEP 567.
Conclusão
contextvars fornece estado contextual isolado para código síncrono e assíncrono. Ele é ideal para IDs de requisição, tracing, locale e pequenos metadados transversais. O uso seguro exige escopos claros, restauração garantida, defaults imutáveis e testes de propagação entre tarefas e threads.







