Curso de Python / Usar iteradores e geradores com yield
Um iterador fornece o próximo item de uma sequência a cada solicitação; uma função geradora permite escrever esse comportamento com yield. Os resultados podem ser calculados conforme a necessidade, mas um iterador esgotado não volta automaticamente ao início.
Distinguir um iterável do percurso que o consome
Um iterável é um objeto do qual se pode obter um iterador. Uma lista é iterável, e iter(lista) cria um objeto que acompanha uma posição de leitura nela. next(iterador) pede o item seguinte; quando não há mais itens, ele levanta StopIteration. Um laço for reconhece esse sinal para terminar a repetição.
Um gerador também é um iterador. Ao chamar uma função que contém yield, você cria o objeto gerador sem executar imediatamente todo o corpo. A execução avança quando algum consumidor solicita um valor. Em yield, devolve um item e fica suspensa, preservando o estado necessário para continuar depois.
Essa pausa difere de return, que termina a chamada. No gerador de somas acumuladas, o mesmo nome local total mantém o subtotal entre uma solicitação e outra. O tutorial de geradores e iteração em Python apresenta esse modelo.
Consumir somas acumuladas passo a passo
Salve o programa completo em generators.py, com UTF-8, numa pasta de prática. Execute python generators.py no terminal com o ambiente virtual selecionado se houver. Em macOS ou Ubuntu sem ambiente virtual, use python3 generators.py quando necessário. Não há dependências externas.
def running_totals(amounts):
total = 0
for amount in amounts:
total += amount
yield total
values = running_totals([3500, 8000, 2500])
print(next(values))
print(list(values))
print(list(values))
print(list(running_totals([3500, 8000, 2500])))
A saída esperada é:
3500
[11500, 14000]
[]
[3500, 11500, 14000]
O primeiro next(values) calcula e consome o subtotal 3500. A chamada seguinte a list(values) avança o mesmo gerador até o fim e reúne apenas os valores restantes: 11500 e 14000. Converter novamente esse objeto esgotado em lista produz []. A última impressão cria outro gerador e, por isso, pode consumir a sequência completa desde o início.
values é o nome do gerador nesse arquivo, não uma lista que armazena todas as somas. Para reler os resultados, crie uma nova passagem sobre uma entrada reutilizável ou armazene os resultados que precisa consultar mais de uma vez. Se a própria entrada for um iterador já consumido, apenas criar outro gerador em torno dela não recupera os dados perdidos.
Comparar armazenamento, posição e expressão geradora
Salve o segundo programa completo em iterator.py e execute python iterator.py, ou python3 iterator.py quando necessário. Nesse arquivo, values é uma lista, separada do iterador criado sobre ela.
values = [1, 2, 3]
iterator = iter(values)
print(next(iterator))
print(list(iterator))
print(list(iterator))
print(list(values))
expression = (n * 2 for n in values)
print(list(expression))
O resultado esperado é:
1
[2, 3]
[]
[1, 2, 3]
[2, 4, 6]
O iterador perde apenas a possibilidade de repetir sua passagem, não os itens da lista original: list(values) ainda contém 1, 2 e 3. A expressão (n * 2 for n in values) cria outro gerador, que calcula os dobros durante o consumo. Uma nova conversão da mesma expressão já esgotada também produziria uma lista vazia.
Gerar resultados sob demanda evita manter de antemão toda a sequência de saída, mas não elimina a memória da entrada que já esteja guardada em uma lista. Além disso, list(gerador) armazena todos os resultados consumidos. Não há uma garantia geral de maior velocidade: a escolha muda o momento da avaliação, a duração do estado e a forma de armazenar os resultados.
Quando um resultado pequeno precisa ser consultado várias vezes, guardá-lo numa lista pode ser mais claro. Quando basta processar cada item uma vez, consumir um fluxo pode evitar um armazenamento intermediário desnecessário. Escolha pela forma de uso, em vez de apenas substituir todos os colchetes por parênteses.
Testar esgotamento, erros adiados e recursos
- No primeiro programa, acrescente uma segunda instrução
print(next(values))logo depois da primeira. A saída inicial passa a 3500 e 11500; a primeira lista restante passa a[14000]. A lista seguinte continua vazia, e o novo gerador da última linha continua completo. Retire a instrução adicional. - Troque somente a primeira chamada por
values = running_totals([]). O primeironext(values)levantaStopIterationantes das impressões seguintes. Para um consumidor em que ausência de dados seja esperada,next(values, None)pode devolver um padrão em vez de levantar o sinal; escolha um padrão que não se confunda com um resultado válido. - Restaure a entrada original e rode o programa completo. Confirme que uma nova chamada a
running_totals([3500, 8000, 2500])cria uma nova passagem. Em outro teste, guardar primeirolist(running_totals(...))permite consultar aquela lista repetidamente, ao custo de armazenar as somas. - No segundo arquivo, acrescente
print(list(expression))depois da impressão existente. A linha adicional é[], pois a primeira conversão já consumiu a expressão geradora. Remova a linha após verificar.
Uma falha no corpo do gerador pode ocorrer durante o consumo, e não quando o objeto é criado. Por exemplo, uma entrada numérica seguida de um texto permite obter um primeiro subtotal e só depois falha na soma seguinte. Coloque o tratamento no ponto em que os resultados são consumidos e decida como informar um processamento apenas parcial. A documentação de next() explica a solicitação de itens e o valor padrão opcional.
Se um gerador abrir um arquivo e ficar suspenso, interromper o laço com break não significa automaticamente fechar esse recurso enquanto o gerador continuar acessível. Prefira um fluxo em que o chamador controla o bloco with e mantenha o consumo dentro da duração do arquivo aberto. Quando o próprio gerador administra o recurso, defina também a finalização antecipada, conforme o comportamento de close() em geradores. A prática de leitura de arquivos com with fornece o contexto para esse controle de duração.




