Geradores assíncronos e outros objetos com método aclose() podem manter conexões, cursores, locks ou blocos finally que precisam ser executados quando o consumo termina. Se um loop sai cedo com break ou exceção, confiar apenas na coleta pode atrasar a limpeza e executá-la fora do contexto assíncrono correto. contextlib.aclosing() transforma esse objeto em um async context manager que chama await objeto.aclose() ao sair.
Neste guia, você aprenderá a fechar geradores assíncronos deterministicamente, tratar saídas antecipadas, preservar context variables e exceções, diferenciar aclosing de closing, integrar com clientes HTTP e bancos e decidir quando o próprio recurso deve implementar __aenter__ e __aexit__.
Primeiro exemplo
from contextlib import aclosing
async with aclosing(gerador_assincrono()) as valores:
async for valor in valores:
print(valor)
Ao terminar o bloco, aclosing aguarda valores.aclose(). Isso ocorre tanto no fim normal quanto diante de exceção.
Saída antecipada com break
async with aclosing(stream()) as itens:
async for item in itens:
if item.pronto:
break
Sem fechamento explícito, o gerador pode permanecer suspenso. Com aclosing, seus blocos finally são executados antes de sair do contexto.
Gerador com finally
async def stream():
recurso = await abrir_recurso()
try:
while True:
item = await recurso.ler()
if item is None:
return
yield item
finally:
await recurso.fechar()
Chamar aclose() injeta o encerramento no gerador e permite que o finally assíncrono seja aguardado.
Por que o contexto importa
A limpeza pode depender de context variables, loop atual, task, tracing e tratamento de exceções. Executá-la dentro do mesmo async with mantém esse contexto previsível.
Implementação conceitual
from contextlib import asynccontextmanager
@asynccontextmanager
async def aclosing_manual(objeto):
try:
yield objeto
finally:
await objeto.aclose()
A função oficial expressa essa intenção de forma padronizada e evita repetir o helper.
Diferença para closing
from contextlib import closing
with closing(recurso) as valor:
usar(valor)
closing() chama um método síncrono close(). aclosing() aguarda aclose(). Não use a versão síncrona para uma coroutine, pois ela não será aguardada corretamente.
O recurso precisa ter aclose
aclosing não verifica uma interface formal antecipadamente. Se o objeto não possuir aclose(), a saída do contexto gera erro. Em APIs públicas, descreva o protocolo e use type hints.
from typing import Protocol
class FechavelAsync(Protocol):
async def aclose(self) -> None: ...
Quando o objeto já é async context manager
Se o recurso implementa __aenter__ e __aexit__, use-o diretamente:
async with cliente.stream() as resposta:
...
aclosing é apropriado para objetos que oferecem aclose(), mas não a interface completa de contexto.
Clientes HTTP
Algumas bibliotecas retornam streams ou respostas que precisam ser fechadas. Sempre siga a API específica: se ela fornece um context manager próprio, ele pode executar mais etapas que apenas aclose(). Use aclosing somente quando o contrato realmente for esse método.
Cursores assíncronos
async with aclosing(cursor) as linhas:
async for linha in linhas:
if corresponde(linha):
return linha
Mesmo com return antecipado, o cursor é fechado. Isso reduz vazamentos de conexões e locks no servidor.
Exceções durante o consumo
async with aclosing(stream()) as itens:
async for item in itens:
processar(item) # pode lançar
O finally do context manager chama aclose durante o desempilhamento. Se a limpeza também falhar, a relação entre exceções segue as regras normais de context managers e exception chaining. Registre ambas sem ocultar a causa original.
Cancelamento
Uma task pode ser cancelada durante o consumo ou durante aclose(). O código de limpeza deve ser curto e tolerar cancelamento. Em recursos críticos, talvez seja necessário usar uma região protegida conforme a biblioteca assíncrona, mas evite bloquear cancelamento indefinidamente.
Timeout de limpeza
Se aclose pode travar por rede, aplique timeout externo:
import asyncio
objeto = stream()
try:
async with aclosing(objeto) as itens:
async for item in itens:
usar(item)
finally:
...
Como aclosing controla o finally internamente, um timeout específico pode exigir um context manager personalizado que envolva await objeto.aclose() em asyncio.timeout().
Idempotência
Idealmente, aclose() deve tolerar múltiplas chamadas ou indicar claramente que o objeto já está fechado. aclosing chama uma vez por entrada de contexto, mas outro proprietário também pode tentar fechar.
Não reutilize após fechar
Geradores assíncronos encerrados não voltam a produzir valores. Documente o ciclo de vida e não armazene o objeto para consumo posterior após o bloco.
Ownership
Quem cria o recurso normalmente é responsável por fechá-lo. Não envolva em aclosing um objeto emprestado que continuará sendo usado por outro componente. Defina propriedade explicitamente nas APIs.
Factory e recurso
async def consumir(factory):
recurso = factory()
async with aclosing(recurso) as itens:
async for item in itens:
...
A factory deixa claro que a função recebe um recurso novo e assume seu fechamento.
Composição com AsyncExitStack
from contextlib import AsyncExitStack, aclosing
async with AsyncExitStack() as stack:
stream_a = await stack.enter_async_context(aclosing(criar_a()))
stream_b = await stack.enter_async_context(aclosing(criar_b()))
...
AsyncExitStack gerencia um número dinâmico de recursos e os fecha em ordem reversa. É útil quando a quantidade depende de configuração.
Composição com asynccontextmanager
Uma API pode encapsular aquisição e limpeza:
from contextlib import asynccontextmanager, aclosing
@asynccontextmanager
async def linhas_do_servico():
async with aclosing(criar_stream()) as stream:
yield stream
O consumidor recebe um context manager de domínio e não precisa conhecer aclose.
Context variables
A documentação destaca que a finalização ocorre no mesmo contexto do consumo. Isso importa quando o finally consulta contextvars para tracing, tenant, idioma ou credenciais temporárias.
Loops aninhados
Se você abre vários geradores, cada um deve ter seu próprio aclosing ou ser registrado em AsyncExitStack. Não dependa de um fechamento implícito ao fim da função.
Generators parcialmente consumidos
O caso mais importante é o consumo parcial. Quando o loop chega naturalmente ao fim, o gerador já encerra. aclosing ainda fornece uma política uniforme para todas as rotas.
Objetos que não são geradores
Qualquer objeto com método assíncrono aclose() pode ser usado, como canais, sessões leves ou wrappers. Verifique se chamar apenas aclose satisfaz todo o contrato de liberação.
Testes
class StreamFalso:
def __init__(self):
self.fechado = False
def __aiter__(self):
return self
async def __anext__(self):
raise StopAsyncIteration
async def aclose(self):
self.fechado = True
Teste término normal, break, return, exceção e cancelamento. Confirme que fechado é verdadeiro e que o callback ocorre na ordem esperada.
Erros comuns
- Usar closing com aclose: a coroutine fica sem await.
- Envolver objeto que já possui contexto próprio: você pode ignorar etapas extras.
- Fechar recurso emprestado: defina ownership.
- Depender da coleta do gerador: o finally pode executar tarde.
- Ignorar cancelamento na limpeza: recursos podem ficar pela metade.
- Reutilizar o stream fechado: o ciclo de vida terminou.
Exemplo completo: busca antecipada
from contextlib import aclosing
async def encontrar_primeiro(factory, predicado):
async with aclosing(factory()) as stream:
async for item in stream:
if predicado(item):
return item
return None
A função retorna assim que encontra um valor, mas o gerador é fechado antes que a coroutine entregue o resultado ao chamador.
Conclusão
contextlib.aclosing() fornece fechamento determinístico para objetos com aclose(), especialmente geradores assíncronos consumidos parcialmente. Ele mantém a limpeza no mesmo contexto de execução e torna break, return e exceções seguros.
A documentação oficial de contextlib.aclosing define a função. Use o context manager próprio do recurso quando existir e recorra a aclosing quando a única interface de liberação for aclose().







