Trilha de aprendizado · Nível 10 · Tutorial 3

Produzir valores sob demanda com yield

Escrever funções geradoras que entregam um valor por solicitação e preservam seu progresso entre suspensões, sem construir antecipadamente uma coleção de resultados.

  • Nível: Intermediário
  • Duração: 18 min
  • 8 passos
Produzir valores sob demanda com yield

O que você vai percorrer

  1. Da função geradora ao objeto gerador Diferencie a função que contém yield, o objeto gerador criado por sua chamada e o momento em que o corpo realmente começa a executar. 2 min
  2. Entregar uma sequência com yield Crie uma função geradora finita que calcula e entrega números sob demanda. 2 min
  3. Rastrear a pausa e a retomada Acompanhe como um gerador preserva sua posição e suas variáveis locais entre chamadas sucessivas de next(). 3 min
  4. Manter percursos independentes Veja como chamadas distintas da mesma função geradora criam percursos de consumo separados. 2 min
  5. Reconhecer o término da geração Entenda quando um gerador entrega seu último valor e quando ele passa a sinalizar que foi esgotado. 2 min
  6. Observar quando os efeitos acontecem Veja como mensagens produzidas no corpo de um gerador acompanham cada avanço, e como uma pausa deixa parte do trabalho pendente. 2 min
  7. Localizar as falhas no avanço Identifique quando uma validação ou falha no corpo de um gerador chega ao consumidor e diferencie esse caso do término normal. 2 min
  8. Aplicar e verificar a produção sob demanda Implemente uma função geradora de quadrados e verifique localmente sua produção incremental, seus estados independentes, seu esgotamento e sua validação adiada. 3 min

O que você vai aprender

  • Distinguir uma função geradora do objeto gerador retornado por sua chamada.
  • Implementar uma sequência de resultados usando yield.
  • Rastrear a suspensão e a retomada do corpo entre chamadas de next.
  • Verificar o término da geração e o momento em que efeitos colaterais ou exceções ocorrem.

Antes de começar

  • Consumir iteradores com iter e next
  • Definir funções com parâmetros e retorno
  • Controlar o escopo e o estado das funções
  • Selecionar casos de teste e verificar exceções

Passo 1 de 8

Da função geradora ao objeto gerador

Diferencie a função que contém yield, o objeto gerador criado por sua chamada e o momento em que o corpo realmente começa a executar.

Uma função que produz sob demanda

Função geradora

Uma função cujo corpo contém yield é uma função geradora. Ela oferece uma forma concisa de criar um iterador, sem escrever manualmente uma classe com __iter__ e __next__.

Nesta definição, primeiro_numero é a função geradora — o corpo ainda não foi executado.

Definição com yield

python
def primeiro_numero(inicio):
    print("Corpo iniciado")
    yield inicio

Três momentos diferentes

Não confunda definir a função, chamá-la e pedir seu próximo valor: são operações distintas.

Diagrama em três etapas: definição de uma função geradora, criação de um objeto gerador e chamada de next que inicia a execução e recebe um valor.

yield caracteriza a função; next(...) é que começa a executar seu corpo.

Chamar cria; next executa

Criação e primeiro avanço

python
gerador = primeiro_numero(10)
print("Gerador criado")

valor = next(gerador)
print(valor)

Ordem observável

A chamada primeiro_numero(10) cria um objeto gerador e o associa a gerador. Ela não imprime "Corpo iniciado".

A primeira chamada de next(gerador) inicia o corpo até alcançar yield. Portanto, a saída é:

Gerador criado
Corpo iniciado
10

O objeto gerador já é um iterador: funciona com iter, next e for, sem que você implemente esses métodos.

Dica

Argumentos não esperam

O corpo da função é adiado, mas os argumentos da chamada são avaliados imediatamente. Em primeiro_numero(calcular_inicio()), calcular_inicio() roda ao criar o gerador; já o corpo de primeiro_numero só começa no primeiro consumo.

Identifique cada operação

Função, objeto e execução

Considere:

def uma_vez(valor):
    print("executando")
    yield valor

fluxo = uma_vez(7)

Qual afirmação está correta logo após a última linha?

Passo 2 de 8

Entregar uma sequência com yield

Crie uma função geradora finita que calcula e entrega números sob demanda.

Uma entrega por vez

Yield entrega e pausa

Dentro de uma função geradora, yield calcula ou obtém um valor, entrega esse valor a quem consome o gerador e pausa a execução. Em um laço, isso permite produzir vários resultados: cada passagem alcança um novo yield.

Diferentemente de criar uma lista com todos os resultados antes, a função abaixo calcula cada dobro quando esse valor é solicitado.

Gerador de dobrados

python
def dobrados(quantidade):
    for numero in range(quantidade):
        yield numero * 2

valores = dobrados(3)

print(next(valores))  # 0
print(next(valores))  # 2
print(next(valores))  # 4

Fluxo de produção incremental

Diagrama mostrando um laço com os números 0, 1 e 2; cada número é multiplicado por 2 e entregue individualmente pelos pontos yield como 0, 2 e 4 ao consumidor.

O laço calcula um resultado e o entrega; os demais resultados permanecem para solicitações posteriores.

Yield não é return

Continuar ou encerrar

yield entrega um valor sem encerrar a função geradora, então o laço pode seguir para outra iteração quando houver uma nova solicitação.

Já return encerra a execução naquele ponto. Se ele substituir yield no corpo do laço, nenhum valor será entregue por essa instrução e não haverá próxima passagem do laço nessa execução.

Exemplo

Troca que muda o comportamento

def com_yield():
    for numero in range(3):
        yield numero

# Produz: 0, depois 1, depois 2


def com_return():
    for numero in range(3):
        return numero

# Encerra na primeira passagem; não produz elementos.

Complete a entrega

Produza os quadrados

Complete a função para entregar os quadrados de 0 até limite - 1, um por solicitação:

def quadrados(limite):
    for numero in range(limite):
        ______ numero * numero

Use no seu computador

Verifique a sequência

No seu editor, crie a função quadrados do exercício. Em seguida, crie gerador = quadrados(4) e execute quatro chamadas a next(gerador). Você deve observar, em ordem: 0, 1, 4 e 9.

A função não monta uma lista de quadrados: o cálculo de cada expressão ocorre durante o consumo, quando a execução alcança seu respectivo yield.

Explique a escolha

Por que trocar yield por return em quadrados impediria a produção dos valores seguintes?

Escreva pelo menos 40 caracteres (0/40).

Passo 3 de 8

Rastrear a pausa e a retomada

Acompanhe como um gerador preserva sua posição e suas variáveis locais entre chamadas sucessivas de next().

O ponto em que a execução fica suspensa

Um yield pausa; não reinicia

Quando next() avança um gerador, o corpo executa até alcançar o próximo yield. Nesse ponto, o valor é entregue e a execução fica suspensa.

As variáveis locais continuam guardadas no objeto gerador. Na próxima chamada de next(), o Python retoma logo depois do yield que entregou o valor anterior — não no início da função.

Um contador preservado entre avanços

Execute uma chamada de cada vez e observe a posição em que o corpo para.

python
def pares_ate(limite):
    atual = 0

    while atual < limite:
        yield atual
        atual += 2

numeros = pares_ate(6)

print(next(numeros))  # 0
print(next(numeros))  # 2

Dica

Atenção à linha após yield

Logo após o primeiro next(numeros), a variável local atual ainda vale 0. A instrução atual += 2 está pendente: ela só será executada quando houver outro avanço do gerador.

Linha do tempo de dois avanços

Estado preservado entre as pausas

O diagrama mostra que cada chamada de next() retoma o mesmo percurso no ponto onde ele havia parado.

Diagrama em três etapas mostrando um gerador que inicia com contador zero, pausa ao entregar zero, retoma para incrementar o contador para dois e pausa novamente ao entregar dois.

Após cada entrega, o contador mantém o valor usado no yield; a atualização posterior só ocorre na retomada.

Exemplo

Rastreamento preciso

  1. Primeiro next(numeros): executa atual = 0, entra no while e entrega 0. Pausa no yield; atual continua sendo 0.
  1. Segundo next(numeros): retoma após o yield, executa atual += 2, volta ao teste do laço e entrega 2. Pausa novamente; agora atual vale 2.

O valor entregue e a próxima instrução pendente são partes diferentes do estado do gerador.

Organize os acontecimentos

Dois avanços consecutivos

Considere o gerador pares_ate(6) e as duas primeiras chamadas de next(numeros). Coloque os acontecimentos na ordem em que ocorrem.

  1. Com atual valendo 2, o laço alcança o próximo yield e entrega 2; o gerador pausa novamente.
  2. O primeiro next() inicia o corpo e alcança yield atual, entregando 0; atual permanece 0 na pausa.
  3. O segundo next() retoma após o primeiro yield e executa atual += 2.

Passo 4 de 8

Manter percursos independentes

Veja como chamadas distintas da mesma função geradora criam percursos de consumo separados.

Uma chamada, um novo percurso

Estados separados

Uma função geradora pode ser chamada várias vezes. Cada chamada cria um novo objeto gerador, com suas próprias variáveis locais e seu próprio ponto de suspensão.

Assim, pares(0) e pares(10) executam o mesmo código, mas não compartilham contador nem progresso.

Duas criações independentes

python
def pares(inicio):
    numero = inicio
    while True:
        yield numero
        numero += 2

primeiros = pares(0)
dez_em_diante = pares(10)

Dois estados de execução

Diagrama com a mesma função pares originando dois objetos geradores separados: primeiros suspenso com numero igual a 0 e dez_em_diante suspenso com numero igual a 10.

Após as chamadas, os dois objetos existem, mas nenhum corpo foi avançado ainda.

Avanços intercalados

Avançar um não altera o outro

Agora, as chamadas a next são intercaladas. Cada uma retoma apenas o objeto recebido como argumento.

Consumo em ordem intercalada

python
print(next(primeiros))      # 0
print(next(dez_em_diante))   # 10
print(next(primeiros))      # 2
print(next(primeiros))      # 4
print(next(dez_em_diante))   # 12

Comparação após o consumo

Linha do tempo com dois percursos independentes: o primeiro entregou 0, 2 e 4 e está preparado para 6; o segundo entregou 10 e 12 e está preparado para 14.

primeiros avançou três vezes; dez_em_diante, duas. Cada contador local conserva seu próprio valor.

Associe cada percurso

Qual é o próximo valor?

Depois do código anterior, associe cada expressão ao resultado que ela produzirá.

Toque em um item e depois no par correspondente.

Passo 5 de 8

Reconhecer o término da geração

Entenda quando um gerador entrega seu último valor e quando ele passa a sinalizar que foi esgotado.

O último valor não é o término

Dois acontecimentos separados

Um yield entrega um elemento e suspende a função. O término normal só é percebido quando uma chamada posterior de next() retoma o corpo e ele chega ao fim — ou encontra um return sem valor.

Por isso, receber o último valor não significa que o consumidor já observou StopIteration: em geral, é preciso pedir mais um valor.

Linha do tempo do último yield

A imagem separa a entrega final do avanço que confirma o esgotamento.

Diagrama em linha do tempo: primeiro next inicia um gerador e recebe o valor 10; segundo next retoma o gerador, encontra o fim do corpo e recebe StopIteration.

O segundo avanço não entrega None; ele informa que não há outro elemento.

Fim do corpo e return sem valor

Dois términos normais

Execute cada bloco separadamente para observar os avanços.

python
def por_fim_do_corpo():
    yield "único valor"


def por_return():
    yield "único valor"
    return

for fabrica in (por_fim_do_corpo, por_return):
    gerador = fabrica()
    print(next(gerador))
    print(next(gerador, "ESGOTADO"))

Nos dois casos, a saída é único valor e depois ESGOTADO. O return sem valor encerra a função; ele não produz None como próximo elemento.

O argumento padrão de next permite observar o esgotamento sem deixar StopIteration interromper o programa.

Dica

Estado permanente

Depois de esgotado, o mesmo objeto gerador continua esgotado. Novos next(gerador, "ESGOTADO") retornam o padrão; ele não volta ao início. Para um novo percurso, chame a função geradora outra vez.

Término antes de qualquer entrega

Gerador vazio por retorno antecipado

Mesmo com yield escrito na função, este caminho pode terminar antes de alcançá-lo.

python
def codigos_positivos(limite):
    if limite <= 0:
        return

    for codigo in range(limite):
        yield codigo

vazio = codigos_positivos(0)
print(next(vazio, "SEM VALORES"))
print(next(vazio, "AINDA ESGOTADO"))

A chamada cria o objeto normalmente. Porém, no primeiro avanço, a condição é atendida e o return encerra o corpo antes de qualquer yield. Assim, a geração é vazia.

Preveja os avanços

Último valor versus esgotamento

Considere:

def pares():
    yield 0
    yield 2

g = pares()
next(g)  # entrega 0

A próxima chamada next(g) lança StopIteration.

Geração vazia

Qual é o resultado de next(g, "fim")?

def talvez_valor(ativo):
    if not ativo:
        return
    yield "disponível"

g = talvez_valor(False)

Passo 6 de 8

Observar quando os efeitos acontecem

Veja como mensagens produzidas no corpo de um gerador acompanham cada avanço, e como uma pausa deixa parte do trabalho pendente.

Criação não executa o corpo

O momento de cada efeito

Um print dentro de uma função geradora só acontece quando a execução chega àquela linha. Portanto, criar o objeto com etapas() não mostra nenhuma mensagem do corpo: o primeiro next() é que inicia a execução.

Depois de um yield, o gerador fica pausado. As linhas abaixo dele continuam pendentes até uma nova solicitação de valor.

Um gerador com pontos observáveis

Execute este exemplo no seu Python 3.

python
def etapas():
    print("início do corpo")
    yield "primeiro"
    print("entre os valores")
    yield "segundo"
    print("fim do corpo")

fluxo = etapas()
print("gerador criado")

print(next(fluxo))
print(next(fluxo))

Linha do tempo do consumo

A execução avança somente até o próximo ponto de suspensão.

Diagrama de linha do tempo mostrando criação do gerador sem mensagens, primeiro next com mensagem de início e entrega do primeiro valor, e segundo next com mensagem intermediária e entrega do segundo valor.

Cada next() retoma o corpo a partir da pausa anterior e pode alcançar novos efeitos.

Pausa deixa trabalho pendente

Consumo parcial

Se você executar apenas next(fluxo) uma vez, verá início do corpo e receberá primeiro. Nesse ponto, o gerador está suspenso no primeiro yield.

Assim, entre os valores e fim do corpo ainda não foram impressos. Elas são instruções pendentes, não mensagens já produzidas.

Organize os acontecimentos

Considere o código da tela anterior e duas chamadas a next(fluxo). Coloque os acontecimentos na ordem em que ocorrem.

  1. O primeiro `next(fluxo)` entrega `primeiro`
  2. É impressa a mensagem `entre os valores`
  3. O segundo `next(fluxo)` entrega `segundo`
  4. É impressa a mensagem `início do corpo`
  5. `fluxo = etapas()` cria o objeto gerador

Observe no seu computador

Experimento de consumo interrompido

No código anterior, deixe somente estas linhas depois de fluxo = etapas():

print("gerador criado")
print(next(fluxo))

Execute o arquivo. Interrompa aí: não faça o segundo next(). Compare a saída com o corpo da função e identifique quais print continuam pendentes.

Relate a pausa

Após executar apenas o primeiro next(fluxo), quais mensagens do corpo apareceram? Quais efeitos ainda não ocorreram e por quê?

Escreva pelo menos 40 caracteres (0/40).

Passo 7 de 8

Localizar as falhas no avanço

Identifique quando uma validação ou falha no corpo de um gerador chega ao consumidor e diferencie esse caso do término normal.

A validação acontece ao avançar

Criar não é executar

Uma chamada a uma função geradora cria o objeto gerador, mas não percorre seu corpo. Portanto, uma validação escrita no corpo só ocorre quando algum consumo — como next() — alcança essa linha.

Se a validação vier antes do primeiro yield, a exceção aparecerá no primeiro avanço, e não na chamada que criou o gerador.

Validação adiada

python
def contar_ate(limite):
    if limite < 0:
        raise ValueError("limite não pode ser negativo")

    for numero in range(limite):
        yield numero

numeros = contar_ate(-2)  # cria o gerador; ainda não há erro
next(numeros)             # ValueError: limite não pode ser negativo

Linha do tempo da validação

Diagrama temporal: a chamada contar_ate menos dois cria um objeto gerador sem executar o corpo; a primeira chamada a next entra no corpo, alcança a condição de limite negativo e entrega ValueError ao consumidor.

A condição é avaliada no primeiro avanço porque é nesse momento que o corpo começa a executar.

Falha não é término normal

Depois de entregar também pode falhar

Uma exceção pode ocorrer depois de valores já entregues. Nesse caso, os avanços anteriores funcionam; a chamada de next() que alcança a falha recebe a exceção.

Quando uma exceção do corpo não é tratada, ela se propaga ao consumidor e aquele objeto gerador fica encerrado. Diferentemente do término normal, não é um sinal de que a sequência terminou como planejado.

Falha no segundo avanço

python
def leituras():
    yield "primeira leitura"
    raise ValueError("leitura inválida")
    yield "esta linha não será alcançada"

fluxo = leituras()
print(next(fluxo))  # primeira leitura
print(next(fluxo))  # ValueError: leitura inválida
# O gerador foi encerrado pela falha.

Atenção

Não lance StopIteration para encerrar

Dentro de uma função geradora, não use raise StopIteration para indicar término. Se esse StopIteration escapar do corpo — inclusive vindo de uma chamada interna — o Python o apresenta ao consumidor como RuntimeError.

Para terminar normalmente, deixe o corpo chegar ao fim ou use return sem valor. Assim, o consumidor observará o término normal como StopIteration ao tentar avançar além dos valores produzidos.

Associe o caso ao resultado

Qual é o resultado observado?

Associe cada situação ao que o consumidor observa.

Toque em um item e depois no par correspondente.

Passo 8 de 8

Aplicar e verificar a produção sob demanda

Implemente uma função geradora de quadrados e verifique localmente sua produção incremental, seus estados independentes, seu esgotamento e sua validação adiada.

Implemente o gerador

Desafio integrado

No seu computador, crie um arquivo Python e implemente quadrados_ate(limite). Ela deve produzir os quadrados de 0 até limite - 1, um por vez, sem montar uma lista. Para tornar a execução observável, mantenha as mensagens antes e depois de cada yield.

A validação do limite fica no corpo da função: por isso, um limite negativo só gera erro quando o objeto é avançado pela primeira vez.

Função a implementar

python
def quadrados_ate(limite):
    if limite < 0:
        raise ValueError("limite não pode ser negativo")

    for numero in range(limite):
        print(f"calculando {numero}")
        yield numero ** 2
        print(f"retomando após {numero}")

Um valor por solicitação

Diagrama mostrando duas chamadas independentes da mesma função geradora. O gerador A já entregou zero e está pausado; o gerador B é criado separadamente e ainda está antes de sua primeira entrega.

Cada chamada cria seu próprio percurso: avançar A não altera a posição de B.

Execute o roteiro de verificação

Cole abaixo da função

Execute este roteiro completo. Leia as mensagens junto com os valores: elas revelam quais trechos do corpo foram realmente alcançados. Observe especialmente que criar negativo não falha; a falha aparece no next(negativo).

Casos para testar localmente

python
a = quadrados_ate(3)
b = quadrados_ate(2)

print("A, primeiro:", next(a))
print("B, primeiro:", next(b))
print("A, segundo:", next(a))
print("A, terceiro:", next(a))

try:
    next(a)
except StopIteration:
    print("A está esgotado")

vazio = quadrados_ate(0)
try:
    next(vazio)
except StopIteration:
    print("Gerador vazio: nenhum valor")

negativo = quadrados_ate(-1)
try:
    next(negativo)
except ValueError as erro:
    print("Erro no avanço:", erro)

Dica

O que conferir

a entrega 0, 1 e 4; b começa novamente em 0, pois tem estado próprio. Depois da última entrega de a, mais um avanço executa a mensagem de retomada pendente e encontra o fim do corpo, causando StopIteration. Um limite zero termina sem entrega; um limite negativo lança ValueError no primeiro avanço.

Relate o que aconteceu

Evidência da execução

Depois de executar o roteiro, relate: quais valores a e b entregaram; o que acontece ao retomar a depois do primeiro valor; e em qual momento ocorrem o esgotamento, o caso vazio e o ValueError.

Escreva pelo menos 180 caracteres (0/180).

Síntese da competência

Resumo

Modelo mental para geradores

  • Definir uma função com yield cria uma função geradora; chamá-la cria um objeto gerador, mas não executa ainda o corpo.
  • Cada next faz o objeto executar até o próximo yield, entrega o valor e preserva a posição e as variáveis locais.
  • Chamadas separadas à mesma função criam objetos com percursos independentes.
  • O fim do corpo, inclusive antes de qualquer yield, é observado como StopIteration; uma validação no corpo ocorre no avanço que a alcança.
  • Após o esgotamento, o mesmo objeto não reinicia: para um novo percurso, faça uma nova chamada da função.

Step concluído

Parabéns! Você concluiu: Produzir valores sob demanda com yield

Você integrou a produção sob demanda com yield: implementou uma sequência incremental, observou pausas e retomadas, conferiu estados independentes e verificou término e validação durante o consumo.

Baixe o Aplicativo agora para ter acesso a + de 5000 cursos gratuitos, exercícios, certificado e muito conteúdo sem pagar nada!

  • Cursos online 100% gratuitos do início ao fim

    Milhares de cursos online em vídeo, ebooks e áudiobooks.

  • Mais de 60 mil exercícios gratuitos

    Para testar seus conhecimentos no decorrer dos cursos online

  • Certificado Digital gratuito válido em todo o Brasil

    Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail

Aplicativo Cursa na tela de ebook, na tela de curso em vídeo e na tela de exercícios do curso, mais o certificado de conclusão de curso