1
resposta

Uso de Future

Olá!

Abaixo está a minha resolução exercício 05. O código é executado pelo arquivo training/main.py desse repositório mas ele não é essencial para esta dúvida. O repositório está aqui: https://github.com/foguinhoperuca/alura-python-introducao-a-linguagem/blob/master/training_py/alura_async.py

Contexto:
Note que eu adotei o uso do asyncio.Future na solução e isso me gerou uma dúvida em comparação ao gabarito proposto: No gabarito a solução acaba sendo um pouco mais simples pois a função assíncrona retorna diretamente para a variável. Essa solução talvez seja adequada, a primeira vista, pela natureza descrita do problema que acaba tendo uma estrutura sequêncial pois o processamento do estoque depende do processamento do pagamento e o problema em si tem uma "cara" mais tradicional do que paralelo.
Por outro lado eu interpretei, inicialmente, o problema como a validação do estoque dependendo do processamento do pagamento (e assumindo que essa tarefa demoraria) faria que a validação do estoque em determinado momento precisasse parar e esperar a aprovação do pagamento.
Entretanto, olhando a minha solução e comparando com o gabarito, ficou uma sensação meio "estranha" como se a minha solução acabasse sendo um overkill pra resolução do problema, no que pese ela funcionar. Talvez tenha faltado executar simultaneamente as tarefas com asyncio.gather para diminuir essa sensação.

Minha dúvida:
Assumindo o overkill da minha solução, qual seria o contexto claro para se utilizar "future"? O quê levar em considereção para que essa solução seja aplicável na hora de analisar o problema?

async def exerc_05() -> None:
     """Descritivo do exercício vai aqui"""

    async def process_payment(order_id: int, future: asyncio.Future) -> None:
        print(f'{colored("[ALURA_ASYNC][05]", "white", attrs=CGATTRS)} processing payment for {colored(order_id, "yellow", attrs=CGATTRS)}')
        order: Dict[str, int | bool] = list(filter(lambda o: o["id"] == order_id, orders))[0]
        logging.debug(f'----- {order} -----')
        await asyncio.sleep(3)
        future.set_result(order['payment_approved'])

    async def validate_stock(order_id: int, future: asyncio.Future) -> None:
        print(f'{colored("[ALURA_ASYNC][05]", "white", attrs=CGATTRS)} validating stock for {colored(order_id, "yellow", attrs=CGATTRS)}')
        order: Dict[str, int | bool] = list(filter(lambda o: o["id"] == order_id, orders))[0]
        logging.debug(f'----- {order} -----')
        await asyncio.sleep(2)
        future.set_result(order['available_stock'])

    print(f'{colored("[ALURA_ASYNC][05]", "white", attrs=CGATTRS)} --- EXERCISE ---')
    orders: List[Dict[str, int | bool]] = [
        {"id": 101, "payment_approved": True,  "available_stock": True},
        {"id": 102, "payment_approved": True,  "available_stock": False},
        {"id": 103, "payment_approved": False, "available_stock": True},
        {"id": 104, "payment_approved": True,  "available_stock": True},
        {"id": 105, "payment_approved": False, "available_stock": False},
    ]
    for ord in orders:
        print(f'{colored("[ALURA_ASYNC][05]", "white", attrs=CGATTRS)} order: {colored(ord, "yellow", attrs=CGATTRS)}')
        fut_pay_appr: asyncio.Future = asyncio.Future()
        asyncio.create_task(process_payment(order_id=ord['id'], future=fut_pay_appr))
        payment_approved: bool = await fut_pay_appr
        if payment_approved:
            print(f'{colored("[ALURA_ASYNC][05]", "white", attrs=CGATTRS)} processing payment {colored("APPROVED", "blue", attrs=CGATTRS)} for {colored(ord["id"], "blue", attrs=CGATTRS)}')
            fut_valid_stock: asyncio.Future = asyncio.Future()
            asyncio.create_task(validate_stock(order_id=ord['id'], future=fut_valid_stock))
            avail_stock: bool = await fut_valid_stock
            if avail_stock:
                print(f'{colored("[ALURA_ASYNC][05]", "white", attrs=CGATTRS)} stock availability {colored("APPROVED", "blue", attrs=CGATTRS)} for {colored(ord["id"], "blue", attrs=CGATTRS)} - {colored("ORDER WILL BE DELIVERED!!", "blue", attrs=CGATTRS)}')
            else:
                print(f'{colored("[ALURA_ASYNC][05]", "white", attrs=CGATTRS)} stock availability {colored("NOT APPROVED", "red", attrs=CGATTRS)} for {colored(ord["id"], "red", attrs=CGATTRS)} - {colored("ORDER IS CANCELED", "red", attrs=CGATTRS)}')
        else:
            print(f'{colored("[ALURA_ASYNC][05]", "white", attrs=CGATTRS)} processing payment {colored("NOT APPROVED", "red", attrs=CGATTRS)} for {colored(ord["id"], "red", attrs=CGATTRS)} - {colored("ORDER IS CANCELED", "red", attrs=CGATTRS)}')

        print(f'{colored("[ALURA_ASYNC][05]", "white", attrs=CGATTRS)} --- </ORDER id={ord["id"]}> ---')
1 resposta

Oi, Jefferson! Tudo bem?

Perceber que sua solução com asyncio.Future funcionou corretamente, mas gerou aquela sensação de overkill, já demonstra uma maturidade importante no aprendizado de programação assíncrona com Python.

Vamos entender melhor o contexto em que o uso do asyncio.Future faz mais sentido. O Future é uma estrutura de baixo nível dentro do asyncio, criada para situações em que o resultado de uma operação será definido de forma manual, em algum momento futuro, por um código externo ao fluxo principal. Exemplos clássicos incluem integração com callbacks de bibliotecas que não são nativas do asyncio, comunicação com drivers de banco de dados assíncronos ou situações em que uma tarefa precisa sinalizar seu resultado para múltiplos consumidores ao mesmo tempo.

No seu exercício, as funções process_payment e validate_stock já são corrotinas nativas do asyncio, e o resultado do payment_approved e do available_stock pode ser simplesmente retornado com return, sem necessidade de instanciar um asyncio.Future manualmente. O gabarito segue exatamente essa linha: como o fluxo entre pagamento e estoque é sequencial por natureza, o problema não exige controle manual sobre o ciclo de vida do resultado.

Sua intuição sobre o asyncio.gather também está correta. Quando as tarefas forem realmente independentes entre si, como processar pedidos de ordens diferentes ao mesmo tempo, o gather seria a escolha mais expressiva e idiomática. Veja um exemplo simples:


import asyncio

async def tarefa1():
    await asyncio.sleep(1)
    return "resultado da tarefa 1"

async def tarefa2():
    await asyncio.sleep(2)
    return "resultado da tarefa 2"

async def main():
    resultados = await asyncio.gather(tarefa1(), tarefa2())
    print(resultados)

asyncio.run(main())

Esse código executa as duas tarefas em paralelo e retorna os resultados juntos, sem precisar gerenciar o asyncio.Future de forma manual. Para o seu exercício com orders, orders e order_id, uma abordagem com gather poderia processar múltiplos pedidos simultaneamente, o que deixaria a solução mais próxima de um cenário real de alta concorrência.

Em resumo: use o asyncio.Future quando precisar de controle explícito sobre quando e como o resultado será definido, especialmente em integrações com código externo ao ecossistema do asyncio. Para fluxos internos, corrotinas comuns com await e asyncio.gather resolvem com mais clareza e menos complexidade.

Dito isso, uma reflexão interessante para continuar evoluindo: você consegue imaginar um cenário real no seu dia a dia de desenvolvimento onde o controle manual do asyncio.Future seria realmente indispensável, em vez de apenas conveniente?

Alura Conte com o apoio da comunidade Alura na sua jornada. Abraços e bons estudos!
Conteúdos relacionados
Ordenando listas no Python
List comprehension Python: como simplificar seu código