Trilha de aprendizado · Nível 10 · Tutorial 5

Delegar a produção de valores com yield from

Decompor uma função geradora em partes menores e encaminhar os elementos produzidos por outros iteráveis usando yield from.

  • Nível: Intermediário
  • Duração: 12 min
  • 7 passos
Delegar a produção de valores com yield from

O que você vai percorrer

  1. Entregar o objeto ou seus elementos Compare yield e yield from aplicados à mesma coleção. 1 min
  2. Substituir o encaminhamento simples Reconheça quando um laço apenas repassa valores e simplifique-o com yield from, preservando a sequência produzida. 2 min
  3. Acompanhar a delegação e a retomada Preveja como o controle passa do consumidor para o gerador externo, para a fonte delegada e de volta ao código externo. 2 min
  4. Distribuir a produção entre subgeradores Divida a produção em funções geradoras auxiliares e coordene a sequência completa com yield from. 2 min
  5. Achatar somente o nível desejado Use um laço externo com yield from para encaminhar os elementos de cada fonte interna, preservando os níveis mais profundos. 2 min
  6. Respeitar o estado de cada fonte Veja como fontes vazias, esgotadas e parcialmente consumidas determinam o que uma delegação com yield from ainda pode produzir. 2 min
  7. Aplicação final: refatorar e verificar Refatore uma produção em lotes com subgeradores e confirme localmente a ordem, a continuação e o respeito ao estado das fontes. 2 min

O que você vai aprender

  • Distinguir yield aplicado a uma coleção de yield from aplicado à mesma coleção.
  • Substituir um laço simples de encaminhamento de elementos por yield from.
  • Combinar subgeradores preservando a ordem de produção e a retomada do gerador externo.
  • Verificar a delegação com fontes vazias e iteradores parcialmente consumidos.

Antes de começar

  • Produzir valores sob demanda com yield
  • Decompor um programa em funções com responsabilidades claras
  • Consultar e atualizar coleções aninhadas
  • Escrever e executar testes de funções com pytest

Passo 1 de 7

Entregar o objeto ou seus elementos

Compare yield e yield from aplicados à mesma coleção.

Um valor composto ou vários valores

A escolha muda o que o gerador produz

Considere a lista ["maçã", "pera", "uva"]. Com yield, a lista inteira é um único valor produzido. Com yield from, o gerador delega a produção para a lista e entrega cada elemento dela separadamente.

Compare as produções

A mesma lista pode sair inteira em um avanço ou ter seus itens entregues em avanços sucessivos.

Diagrama comparando um gerador que entrega uma lista inteira de frutas de uma vez e outro que entrega maçã, pera e uva uma por vez.

yield frutas produz um objeto lista; yield from frutas produz os elementos da lista.

Duas funções, duas sequências

yield entrega a lista como um valor

Execute cada função e observe o resultado de list(...).

python
def com_yield():
    frutas = ["maçã", "pera", "uva"]
    yield frutas

print(list(com_yield()))
# [['maçã', 'pera', 'uva']]

yield from entrega os elementos

A fonte delegada continua sendo a mesma lista.

python
def com_yield_from():
    frutas = ["maçã", "pera", "uva"]
    yield from frutas

print(list(com_yield_from()))
# ['maçã', 'pera', 'uva']

Dica

Leia os colchetes com atenção

No primeiro resultado, há uma lista dentro da lista externa criada por list(...): foi produzido um único valor, que por sua vez é uma lista. No segundo, os três textos foram produzidos individualmente.

Preveja o resultado

Qual sequência será exibida?

Considere:

<code>def pacote():
yield [10, 20]

def itens():
yield from [10, 20]

print(next(pacote()))
print(list(itens()))</code>

Quais valores são exibidos, nessa ordem?

Passo 2 de 7

Substituir o encaminhamento simples

Reconheça quando um laço apenas repassa valores e simplifique-o com yield from, preservando a sequência produzida.

Quando o laço só encaminha

Mesma produção, escrita mais direta

Quando um gerador percorre uma fonte e entrega cada elemento sem modificá-lo, o laço for pode ser substituído por yield from. Nos dois casos, o consumidor recebe os mesmos valores, na mesma ordem, ao usar next() ou for.

Antes e depois

python
def encaminhar_com_for(fonte):
    for item in fonte:
        yield item


def encaminhar_com_yield_from(fonte):
    yield from fonte

print(list(encaminhar_com_for(["a", "b", "c"])))
print(list(encaminhar_com_yield_from(["a", "b", "c"])))
# Ambas produzem: ['a', 'b', 'c']

O caminho dos valores

Diagrama comparando dois caminhos equivalentes: uma lista fornece os elementos a um laço for, que os entrega um por um; ao lado, a mesma lista fornece os elementos diretamente por yield from. As duas saídas mostram a, b e c na mesma ordem.

yield from fonte substitui o encaminhamento simples feito por for item in fonte: yield item.

Fontes que podem ser delegadas

A fonte é qualquer iterável

yield from aceita uma coleção, um iterador ou outro objeto gerador. A delegação começa no estado atual da fonte: se você passar um iterador que já avançou, apenas os elementos restantes serão encaminhados.

Lista, iterador e gerador

python
def encaminhar(fonte):
    yield from fonte

numeros = [1, 2, 3]                 # coleção
itens = iter([10, 20, 30])          # iterador
next(itens)                         # consome 10
quadrados = (n * n for n in [2, 3]) # objeto gerador

print(list(encaminhar(numeros)))    # [1, 2, 3]
print(list(encaminhar(itens)))      # [20, 30]
print(list(encaminhar(quadrados)))  # [4, 9]

Dica

Critério prático

Faça a substituição somente se o corpo do laço for, na prática, apenas yield item. A forma curta comunica que este gerador não altera os elementos: ele só delega sua produção.

Refatore o encaminhamento

Complete a refatoração

Complete o corpo para substituir o laço por delegação:

def registros_ativos(registros):
    ____ registros

A função deve produzir os mesmos valores que for registro in registros: yield registro.

Quando manter o laço

Nem todo for com yield é só encaminhamento

Não remova o laço se ele transforma, filtra ou executa uma ação para cada elemento. Nesses casos, o corpo faz trabalho próprio antes de entregar algo; yield from fonte entregaria os valores originais sem esse trabalho.

Transformação exige o laço

python
def dobrar(numeros):
    for numero in numeros:
        yield numero * 2

print(list(dobrar([1, 2, 3])))
# [2, 4, 6]

# yield from numeros NÃO seria equivalente:
# produziria [1, 2, 3], sem dobrar os valores.

Escolha o caso refatorável

Em qual função a substituição direta por yield from fonte preserva os valores produzidos?

Passo 3 de 7

Acompanhar a delegação e a retomada

Preveja como o controle passa do consumidor para o gerador externo, para a fonte delegada e de volta ao código externo.

Quem produz cada valor?

Uma chamada por vez

Considere um gerador externo que entrega um valor inicial, delega dois valores e depois entrega um valor final. Enquanto a fonte delegada ainda tem elementos, cada next() do consumidor recebe um elemento dela. O código após yield from permanece suspenso.

Gerador com delegação

python
def sequencia():
    yield "início"
    yield from ["A", "B"]
    yield "fim"

fluxo = sequencia()

print(next(fluxo))  # início
print(next(fluxo))  # A
print(next(fluxo))  # B
print(next(fluxo))  # fim

Caminho das chamadas

Diagrama de fluxo mostrando quatro chamadas do consumidor: a primeira recebe início do gerador externo; a segunda e a terceira atravessam yield from e recebem A e B da lista; a quarta, após o esgotamento da lista, retorna ao gerador externo e recebe fim.

A delegação encaminha a demanda do consumidor à fonte, elemento por elemento.

O último elemento não encerra a delegação ainda

O avanço seguinte confirma o esgotamento

Na terceira chamada, next(fluxo) entrega "B", o último elemento da lista. Nesse momento, o consumidor ainda recebeu um valor da fonte delegada. Somente na chamada seguinte Python tenta avançar a fonte novamente, detecta seu esgotamento e retoma o código logo após yield from; por isso essa quarta chamada produz "fim".

Dica

Regra de previsão

Pense em yield from fonte como: “continue pedindo o próximo valor à fonte”. O trecho posterior só pode executar quando uma nova tentativa de avanço percebe que a fonte acabou.”

Ordene os eventos

Sequência de execução

Usando a função sequencia() mostrada, coloque os eventos na ordem em que ocorrem ao chamar next(fluxo) quatro vezes.

  1. A fonte delegada entrega "B", seu último elemento.
  2. Uma nova tentativa detecta o esgotamento da fonte, e o gerador externo entrega "fim".
  3. A fonte delegada entrega "A".
  4. O gerador externo entrega "início".

Passo 4 de 7

Distribuir a produção entre subgeradores

Divida a produção em funções geradoras auxiliares e coordene a sequência completa com yield from.

Quem produz e quem coordena

Duas responsabilidades claras

Uma função auxiliar pode produzir apenas um trecho da sequência. Ao chamá-la, você obtém um objeto gerador: esse é o subgerador.

A função externa coordena os trechos. Ela não precisa repetir a lógica de cada produção: delega a cada subgerador com yield from.

Composição sequencial

Observe que a coordenação passa integralmente pelo primeiro subgerador antes de iniciar o segundo.

Diagrama com um gerador coordenador delegando primeiro a um subgerador que produz os valores A e B e, após seu término, a outro que produz C e D; a saída final é A, B, C, D.

Cada subgerador termina antes de a delegação seguinte começar.

Delegar às funções auxiliares

Funções produtoras e coordenadora

Cada chamada, como cabecalho(), fornece o subgerador usado pela delegação.

python
def cabecalho():
    yield "início"
    yield "itens"


def rodape():
    yield "fim"


def relatorio():
    yield from cabecalho()
    yield from rodape()


print(list(relatorio()))
# ['início', 'itens', 'fim']

Ordem sem intercalação

relatorio() delega a cabecalho() até ela se esgotar. Só então alcança yield from rodape().

Assim, os valores não se alternam automaticamente entre os subgeradores: a composição é sequencial.

Pratique a refatoração

Separe e coordene

Refatore esta produção em duas funções auxiliares e uma coordenadora, preservando exatamente a ordem produzida:

<code>def numeros():
yield 2
yield 4
yield 1
yield 3</code>

Uma auxiliar deve produzir 2 e 4; a outra, 1 e 3. A coordenadora deve usar yield from para reuni-las.

Escreva pelo menos 80 caracteres (0/80).

Passo 5 de 7

Achatar somente o nível desejado

Use um laço externo com yield from para encaminhar os elementos de cada fonte interna, preservando os níveis mais profundos.

Um nível por vez

Delegue a cada fonte interna

Quando uma coleção contém outras fontes, percorra primeiro a coleção externa. Em cada volta, use yield from na fonte interna atual.

Assim, o gerador entrega os elementos de cada fonte em sequência: termina uma fonte antes de começar a próxima.

O caminho dos elementos

O laço externo escolhe uma fonte interna por vez; yield from entrega os elementos dela.

Diagrama de uma lista externa contendo três listas internas, com setas mostrando cada lista interna sendo percorrida em ordem e seus elementos saindo em um fluxo único.

A coleção externa organiza as fontes; a delegação produz os elementos de cada fonte.

Gerador para achatar um nível

Delegação dentro do laço externo

Experimente no seu editor e observe o resultado de list(achatar_um_nivel(...)).

python
def achatar_um_nivel(fontes):
    for fonte in fontes:
        yield from fonte

lotes = [["A", "B"], ["C"], ["D", "E"]]
print(list(achatar_um_nivel(lotes)))
# ['A', 'B', 'C', 'D', 'E']

Atenção

Não delegue à coleção externa diretamente

yield from fontes entrega cada fonte interna como um único elemento. Para entregar o conteúdo de cada fonte interna, o yield from precisa ficar dentro do for.

Profundidade preservada

O gerador não continua achatando sozinho

O laço percorre apenas as fontes no nível externo. Se uma delas contém outra lista, essa lista mais profunda é entregue como um único elemento — ela não é percorrida automaticamente.

Uma lista ainda aninhada

A lista [2, 3] pertence à primeira fonte interna. Por isso, ela permanece agrupada na saída.

python
dados = [[1, [2, 3]], [4, 5]]
print(list(achatar_um_nivel(dados)))
# [1, [2, 3], 4, 5]

Confira a saída

Achatamento de um nível

Considere a função achatar_um_nivel e a chamada abaixo. Qual é a saída?

list(achatar_um_nivel([["a", ["b", "c"]], ["d"]]))

Passo 6 de 7

Respeitar o estado de cada fonte

Veja como fontes vazias, esgotadas e parcialmente consumidas determinam o que uma delegação com yield from ainda pode produzir.

A fonte não é reiniciada

Delegue o estado atual

yield from fonte consome a fonte recebida exatamente como ela está. Se ela estiver vazia ou já esgotada, nenhum valor é entregue nessa etapa. Ainda assim, o gerador externo prossegue para as instruções seguintes.

O ponto atual da fonte importa

A delegação consulta a fonte no estado em que ela foi recebida.

Diagrama com três fontes delegadas: uma vazia sem saídas, uma esgotada sem saídas e um iterador parcialmente consumido emitindo apenas seus dois itens restantes; todas levam a uma saída posterior do gerador externo.

Fontes sem elementos não produzem nada, mas não impedem a continuação do gerador externo.

Fonte vazia ou esgotada

A continuação ainda é produzida

Execute este código e observe que as duas fontes não acrescentam valores, mas "fim" continua acessível.

python
def relatorio(fonte):
    yield "início"
    yield from fonte
    yield "fim"

print(list(relatorio([])))

numeros = iter([10, 20])
list(numeros)  # esgota o iterador
print(list(relatorio(numeros)))

Exemplo

Saída esperada

['início', 'fim']
['início', 'fim']

Uma lista vazia já não tem elementos. O iterador numeros também não tem: ele foi consumido por list(numeros) antes de ser delegado.

Consumo parcial começa do ponto atual

Delegar o restante

Neste caso, next() já retirou o primeiro valor do iterador antes da chamada de sequencia.

python
def sequencia(fonte):
    yield "antes"
    yield from fonte
    yield "depois"

itens = iter(["A", "B", "C"])
print(next(itens))
print(list(sequencia(itens)))

Exemplo

Previsão confirmada

A
['antes', 'B', 'C', 'depois']

yield from itens não faz cópia e não volta para "A": delega somente os elementos restantes.

Dica

Coleção e iterador não têm o mesmo estado

Ao delegar uma lista diretamente, cada nova chamada de sequencia(lista) obtém um novo percurso da lista. Ao reutilizar o mesmo iterador, a próxima chamada continua de onde o consumo anterior parou — ou não produz nada, se ele já estiver esgotado.

Verifique sua previsão

Iterador parcialmente consumido

Considere o código:

fonte = iter([1, 2, 3])
next(fonte)


def produzir():
    yield from fonte

list(produzir()) == [2, 3]

A expressão final é verdadeira?

Passo 7 de 7

Aplicação final: refatorar e verificar

Refatore uma produção em lotes com subgeradores e confirme localmente a ordem, a continuação e o respeito ao estado das fontes.

Do encaminhamento manual à delegação

Uma função coordenadora, responsabilidades separadas

Você vai transformar uma função que percorre lotes manualmente em uma versão composta por auxiliares. Cada auxiliar produz um trecho; a função coordenadora percorre os lotes e delega os elementos de cada um.

Use yield quando o resultado desejado for o próprio objeto composto. Use yield from quando quiser encaminhar os elementos da fonte.

Fluxo da composição

A coordenação percorre os lotes externos; cada delegação esgota somente o lote atual antes de começar o próximo.

Diagrama de três lotes sequenciais: o primeiro contém A e B, o segundo está vazio e o terceiro contém C e D. Setas mostram cada lote sendo delegado para uma saída final na ordem A, B, C, D.

O lote vazio não produz valores, mas a coordenadora segue para o lote seguinte.

Refatore e execute localmente

Crie um arquivo de teste

No seu computador, crie o arquivo test_lotes.py com o conteúdo abaixo. Primeiro, execute pytest -q: a implementação inicial deve falhar em um teste. Depois, substitua somente o corpo de produzir_lotes pela versão indicada no comentário e execute o comando novamente.

test_lotes.py

python
import pytest


def produzir_lote(lote):
    yield from lote


def produzir_lotes(lotes):
    # Versão inicial: entrega cada lote como um único objeto.
    # Refatore este corpo para encaminhar os elementos de cada lote.
    for lote in lotes:
        yield lote


def com_marcadores(fonte):
    yield "inicio"
    yield from fonte
    yield "fim"


def test_produzir_lotes_une_um_nivel_na_ordem():
    lotes = [["A", "B"], [], ["C"]]
    assert list(produzir_lotes(lotes)) == ["A", "B", "C"]


def test_continua_depois_de_fonte_vazia():
    assert list(com_marcadores([])) == ["inicio", "fim"]


def test_iterador_parcial_comeca_na_posicao_atual():
    numeros = iter([10, 20, 30])
    assert next(numeros) == 10
    assert list(produzir_lote(numeros)) == [20, 30]


# Substituição esperada para o corpo de produzir_lotes:
# for lote in lotes:
#     yield from produzir_lote(lote)

Confira o contrato observado

Explique os dois casos-limite

Após executar os testes, explique: por que a fonte vazia ainda permite a produção de "fim"? E por que o iterador numeros não volta a entregar 10?

Escreva pelo menos 80 caracteres (0/80).

Resumo

Síntese para usar no código

  • Use yield para produzir um valor único — inclusive uma lista ou outro objeto composto.
  • Use yield from para delegar a produção dos elementos de um iterável, iterador ou subgerador.
  • Para achatar um nível, percorra as fontes externas e delegue cada fonte interna; a ordem é preservada.
  • Uma fonte vazia simplesmente termina sua delegação. Um iterador parcial continua da posição atual, sem cópia nem reinício.
  • Verifique refatorações comparando os valores produzidos e incluindo casos de ordem, fonte vazia, continuação e consumo parcial.

Competência concluída

Tutorial concluído

Parabéns! Você concluiu: Delegar a produção de valores com yield from

Muito bem! Agora você consegue decidir entre entregar um objeto com yield e encaminhar os elementos de uma fonte com yield from, preservando ordem e estado de 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