contextlib.aclosing: feche geradores async

Publicado em: 30/08/2026
Tempo de leitura: 5 minutos
Close-up view of a computer screen displaying code in a software development environment.

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().

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    A developer typing code on a laptop with a Python book beside in an office.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    weakref.finalize: limpeza automática sem reter objetos

    Aprenda weakref.finalize no Python para limpar recursos sem manter objetos vivos, usando close, detach, alive e callbacks seguros.

    Ler mais

    Tempo de leitura: 6 minutos
    30/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    SimpleNamespace: objetos leves com atributos

    Aprenda SimpleNamespace no Python para criar objetos leves por atributos, converter dicionários e escolher entre dataclass e TypedDict.

    Ler mais

    Tempo de leitura: 5 minutos
    29/08/2026
    Close-up of a detailed South America map showcasing geography and cartography.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    ChainMap no Python: mapas em camadas

    Aprenda ChainMap no Python para combinar configurações e escopos em camadas, controlar precedência, escrita e snapshots.

    Ler mais

    Tempo de leitura: 6 minutos
    29/08/2026
    Close-up of a python snake curled up, showcasing its detailed scales.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    itertools.pairwise: analise pares consecutivos

    Aprenda itertools.pairwise no Python para analisar pares consecutivos, calcular deltas, detectar transições e validar sequências.

    Ler mais

    Tempo de leitura: 5 minutos
    29/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    itertools.batched: processe iteráveis em lotes

    Aprenda itertools.batched no Python para processar iteráveis em lotes, controlar memória, usar strict e criar pipelines resilientes.

    Ler mais

    Tempo de leitura: 6 minutos
    29/08/2026
    Detailed shot of a Jungle Carpet Python (Morelia spilota cheynei) in its natural habitat.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    cmp_to_key: adapte comparadores antigos ao sorted

    Aprenda cmp_to_key no Python para adaptar comparadores antigos, ordenar com locale, preservar estabilidade e evitar regras inconsistentes.

    Ler mais

    Tempo de leitura: 5 minutos
    29/08/2026