Python yield Palavra-chave: O que é e como usá-lo?
A palavra-chave yield em Python transforma uma função regular em um gerador, que produz uma sequência de valores sob demanda em vez de computá-los todos de uma vez.
As funções Python nem sempre têm uma declaração return. As funções geradoras são funções que têm a palavra-chave yield em vez de return.
Essas funções produzem iteradores de geradores, que são objetos que representam um fluxo de dados. Os elementos representados por um iterador são criados e cedidos somente quando necessário. Esse tipo de avaliação é geralmente chamado de avaliação preguiçosa.
Ao lidar com grandes conjuntos de dados, os geradores oferecem uma alternativa eficiente em termos de memória para o armazenamento de dados em listas, tuplas e outras estruturas de dados que exigem espaço na memória para cada um de seus elementos. As funções geradoras também podem criar iteradores infinitos, o que não é possível com estruturas avaliadas com ansiedade, como listas e tuplas.
Antes de começarmos, vamos recapitular as diferenças entre funções e geradores:
Recurso
Função
Gerador
Produção de valor
Retorna todos os valores de uma só vez
Produz valores um de cada vez, sob demanda
Execução
Executa completamente antes de retornar
Faz uma pausa depois de ceder, retoma quando o próximo valor é solicitado
Palavra-chave
retorno
rendimento
Uso da memória
Potencialmente alto, armazena toda a sequência na memória
Baixo, armazena apenas o valor e o estado atuais para o próximo
Iteração
Várias iterações são possíveis, mas requerem o armazenamento de toda a sequência
Projetado para iteração de passagem única, mais eficiente para sequências grandes ou infinitas
Usando a função Python yield para criar funções de gerador
O termo gerador em Python pode se referir a um iterador gerador ou a uma função geradora. Esses são objetos diferentes, mas relacionados em Python. Neste tutorial, os termos completos são usados com frequência para evitar confusão.
Primeiro, vamos explorar as funções do gerador. Uma função geradora é semelhante a uma função normal, mas contém a palavra-chave yield em vez de return.
Quando um programa Python chama uma função geradora, ele cria um iterador gerador. Os iteradores produzem um valor sob demanda e pausam sua execução até que outro valor seja necessário. Vamos dar uma olhada em um exemplo para explicar esse conceito e demonstrar a diferença entre funções regulares e funções geradoras.
Usando uma função regular
Primeiro, vamos definir uma função regular, que contém uma instrução return. Essa função aceita uma sequência de palavras e uma letra e retorna uma lista contendo o número de ocorrências da letra em cada palavra:
deffind_letter_occurrences(words, letter):
output =[]for word in words:
output.append(word.count(letter))return output
print(
find_letter_occurrences(["apple","banana","cherry"],"a"))Tecnologia
A função gera uma lista contendo 1, 3 e 0, pois há um a em apple, três ocorrências de a em banana e nenhuma em cherry. A mesma função pode ser refatorada para usar compreensões de lista em vez de inicializar uma lista vazia e usar.append():
deffind_letter_occurrences(words, letter):return[word.count(letter)for word in words]Tecnologia
Essa função regular retorna uma lista contendo todos os resultados sempre que é chamada. No entanto, se a lista de palavras for grande, chamar essa função regular exige muito da memória, pois o programa cria e armazena uma nova lista do mesmo tamanho que a original. Se essa função for usada repetidamente em vários argumentos de entrada, ou se funções semelhantes estiverem realizando outras operações nos dados originais, a pressão na memória poderá aumentar rapidamente.
Usando uma função de gerador
Em vez disso, você pode usar uma função geradora:
deffind_letter_occurrences(words, letter):for word in words:yield word.count(letter)
words =["apple","banana","cherry"]
letter ="a"
output = find_letter_occurrences(words, letter)print(output)Tecnologia
<generator object find_letter_occurrences at 0x102935e00>Tecnologia
A função inclui a palavra-chaveyield em vez de return. Essa função de gerador retorna um objeto gerador quando chamada, que é atribuído a output. Esse objeto é um iterador. Ele não contém os dados que representam o número de ocorrências da letra em cada palavra. Em vez disso, o gerador criará e produzirá os valores quando necessário. Vamos buscar o primeiro valor desse iterador gerador:
A função integrada next() é uma maneira de obter o próximo valor de um iterador. Veremos outras maneiras mais adiante neste tutorial.
O código na função geradora é executado até que o programa atinja a linha com a palavra-chave yield. Neste exemplo, o loop for inicia sua primeira iteração e busca o primeiro elemento da lista words. O método de cadeia de caracteres .count() retorna um número inteiro, que, nesse caso, é 1, pois há uma ocorrência de a em apple. O gerador produz esse valor, que é retornado por next(output).
O gerador output interrompe sua execução nesse ponto. Portanto, o gerador concluiu a primeira iteração do loop for e encontrou o número de ocorrências da letra a na primeira palavra da lista de palavras. Agora, ele está aguardando até que seja necessário novamente.
Se o next() incorporado for chamado novamente com output como argumento, o gerador retomará a execução a partir do ponto em que foi pausado:
O gerador continua a partir da linha com yield na primeira iteração do loop for. Como o loop for não tem mais linhas de código, ele retorna ao topo do loop e busca o segundo elemento da lista words. O valor retornado por .count() é 3 nesse caso, e esse valor é gerado. O gerador faz uma nova pausa nesse ponto da execução.
A terceira chamada para next() retoma essa execução:
A segunda iteração chega ao final do loop for, que passa para a terceira iteração. O código avança para a linha com yield novamente, desta vez produzindo o inteiro 0, pois não há nenhuma ocorrência de a em cherry.
O gerador faz uma nova pausa. O programa só determina o destino do gerador quando chamamos next() uma quarta vez:
A execução é retomada a partir do final da terceira iteração do loop for. No entanto, o loop chegou ao fim de sua iteração, pois não há mais elementos na lista words. O gerador gera uma exceção StopIteration.
Na maioria dos casos de uso, os elementos do gerador não são acessados diretamente usando next(), mas por meio de outro processo de iteração. A exceção StopIteration sinaliza o fim do processo de iteração. Exploraremos isso mais detalhadamente na próxima seção deste tutorial.
O Python tem outra maneira de criar iteradores geradores quando sua operação pode ser representada por uma única expressão, como no exemplo anterior. O iterador do gerador output pode ser criado usando uma _expressão do gerador_:
words =["apple","banana","cherry"]
letter ="a"
output =(word.count(letter)for word in words)print(next(output))print(next(output))print(next(output))print(next(output))Tecnologia
A expressão entre parênteses atribuída a output é uma expressão geradora, que cria um iterador gerador semelhante ao produzido pela função geradora find_letter_occurrences().
Vamos concluir esta seção com outro exemplo de uma função geradora para destacar como a execução é pausada e retomada sempre que um elemento é necessário:
defshow_status():print("Start")yieldprint("Middle")yieldprint("End")yield
status = show_status()next(status)Tecnologia
Essa função geradora não tem um loop. Em vez disso, ele contém três linhas que têm a palavra-chave yield. O código cria um iterador gerador status quando você chama a função geradora show_status(). Na primeira vez que o programa chama next(status), o gerador inicia a execução. Ele imprime a string "Start" e faz uma pausa após a primeira expressão yield. O gerador gera None, pois não há nenhum objeto após a palavra-chave yield.
O programa imprime a cadeia de caracteres "Middle" somente quando next() é chamado uma segunda vez:
O gerador faz uma pausa na expressão final yield. Você abrirá uma exceção StopIteration na próxima vez que o programa solicitar um valor desse iterador gerador:
Exploraremos mais maneiras de usar geradores na seção a seguir.
Trabalhando com iteradores de gerador
As funções de gerador criam iteradores de gerador, e os iteradores são iteráveis. Vamos analisar essa frase. Toda vez que o programa chama uma função geradora, ele cria um iterador. Como os iteradores são iteráveis, eles podem ser usados em for loops e outros processos iterativos.
Portanto, a função integrada next() não é a única maneira de acessar elementos em um iterador. Esta seção explora outras formas de trabalhar com geradores.
Usando o protocolo de iteração do Python com iteradores de gerador
Vamos revisitar uma função geradora de uma seção anterior deste tutorial:
deffind_letter_occurrences(words, letter):for word in words:yield word.count(letter)
words =["apple","banana","cherry"]
letter ="a"
output = find_letter_occurrences(words, letter)for value in output:print(value)Tecnologia
Em vez de usar next() várias vezes, essa versão do código usa o iterador do gerador output em um loop for. Como os iteradores são iteráveis, eles podem ser usados em for loops. O loop busca itens no iterador do gerador até que não haja mais valores.
Ao contrário das estruturas de dados, como listas e tuplas, um iterador só pode ser usado uma vez. O código não imprime os valores novamente se tentarmos executar o mesmo loop for uma segunda vez:
deffind_letter_occurrences(words, letter):for word in words:yield word.count(letter)
words =["apple","banana","cherry"]
letter ="a"
output = find_letter_occurrences(words, letter)print("First attempt:")for value in output:print(value)print("Second attempt:")for value in output:print(value)Tecnologia
O iterador é exaurido pelo primeiro loop for, portanto, não pode mais gerar valores. Se o gerador for necessário novamente depois de esgotado, devemos criar outro iterador de gerador a partir da função de gerador.
Também é possível que você tenha vários iteradores de geradores em um programa ao mesmo tempo:
deffind_letter_occurrences(words, letter):for word in words:yield word.count(letter)
words =["apple","banana","cherry"]
letter ="a"
first_output = find_letter_occurrences(words, letter)
second_output = find_letter_occurrences(words, letter)print("First value of first_output:")print(next(first_output))print("Values of second_output:")for value in second_output:print(value)print("Remaining values of first_output:")for value in first_output:print(value)Tecnologia
First value of first_output:
1
Values of second_output:
1
3
0
Remaining values of first_output:
3
0Tecnologia
A função geradora find_letter_occurrences() cria dois iteradores de gerador: first_output e second_output. Embora ambos os iteradores se refiram aos mesmos dados na lista words, eles progridem independentemente um do outro.
Este exemplo obtém o primeiro valor de first_output usando next(). O iterador do gerador produz 1 e faz uma pausa nesse ponto. O programa faz um loop em second_output next. Como esse gerador ainda não produziu nenhum valor, o loop percorre todos os valores produzidos pelo segundo iterador. Por fim, há outro loop for iterando por first_output. No entanto, esse iterador já produziu seu primeiro valor anteriormente no programa. O loop percorre os valores restantes nesse iterador.
O loop for não é o único processo que pode ser usado para iterar por meio de iteradores de geradores:
Nesses exemplos, o programa chama a função do gerador diretamente para criar e usar o iterador do gerador em vez de atribuí-lo a uma variável. No primeiro exemplo, o iterador é descompactado usando a notação em estrela. Esse processo se baseia no mesmo protocolo de iteração que o loop for.
No segundo exemplo, o iterador do gerador é passado para o built-in sorted(), que requer um argumento iterável. Os geradores são iteráveis e, portanto, podem ser usados sempre que a iteração do Python ocorrer.
Criação de iteradores infinitos
Um gerador produz um valor e faz uma pausa até que o próximo valor seja necessário. Toda vez que o código solicitar um valor de um iterador, o código na função geradora será executado até que a próxima expressão yield seja avaliada. Em todos os exemplos deste tutorial até agora, a função geradora tinha um número finito de expressões yield. No entanto, é possível criar um gerador que produza um número infinito de valores usando um loop while na função geradora. No exemplo a seguir, o gerador produz uma cor aleatória da lista de cores passada para a função geradora:
import random
defget_color(colors):whileTrue:yield random.choice(colors)
output_colors = get_color(["red","green","blue"])print("First two colors:")print(next(output_colors))print(next(output_colors))print("Next 10 colors using a 'for' loop:")for _ inrange(10):print(next(output_colors))Tecnologia
First two colors:
green
red
Next 10 colors using a 'for' loop:
blue
green
green
green
red
red
red
blue
green
redTecnologia
A função geradora get_color() tem uma expressão yield em um loop while. Portanto, o código sempre encontrará outra expressão yield ao procurar o próximo valor. O iterador gerador output_colors produz um número infinito de cores escolhidas aleatoriamente na lista de entrada. Esse gerador nunca se esgotará.
Não é possível criar estruturas de dados infinitas, como listas e tuplas. Os geradores permitem que um programa crie iteráveis infinitos. Observe que se o iterador do gerador for usado diretamente em um loop for, o loop será executado para sempre.
Conceitos avançados de geradores
Os geradores têm casos de uso mais avançados em Python. Esta seção explorará alguns deles.
Envio de um objeto para o gerador
Os geradores também podem aceitar dados adicionais que podem ser usados durante a avaliação do código. A instrução que contém a palavra-chave yield é uma expressão que é avaliada como um valor. Esse valor pode ser atribuído a uma variável dentro da função geradora. Vamos começar com um exemplo básico para demonstrar esse conceito:
defgenerator_function():
value =yield1print(f"The yield expression evaluates to: {value}")
value =yield2print(f"The yield expression evaluates to: {value}")
output = generator_function()print(next(output))print(next(output))print(next(output))Tecnologia
1
The yield expression evaluates to: None
2
The yield expression evaluates to: None
Traceback (most recent call last):
...
StopIterationTecnologia
A palavra-chave Python yield cria uma expressão que é avaliada como um valor. No entanto, o valor dessa expressão na função geradora não é o mesmo objeto gerado pelo gerador. Considere a primeira expressão yield. O gerador produz o número inteiro 1. Portanto, o site print(next(output)) exibe 1 na primeira vez em que é chamado e pausa a execução do gerador.
No entanto, a expressão yield no gerador é avaliada como um objeto, que o código atribui ao nome da variável value. Neste exemplo, yield atribui None a value. Esse processo é repetido para a segunda ocorrência de yield na função geradora. O objetivo da terceira chamada next() é garantir que todo o código na função geradora seja executado.
Vamos substituir a segunda e a terceira chamadas para next() por.send()que é um método na classe do gerador:
defgenerator_function():
value =yield1print(f"The yield expression evaluates to: {value}")
value =yield2print(f"The yield expression evaluates to: {value}")
output = generator_function()print(next(output))print(output.send("Here's a value"))print(output.send("Here's another value"))Tecnologia
1
The yield expression evaluates to: Here's a value
2
The yield expression evaluates to: Here's another value
Traceback (most recent call last):
...
StopIterationTecnologia
A função do gerador não foi alterada. O gerador é iniciado chamando next(), e o código é executado até produzir o primeiro número inteiro, 1. Em vez de usar next() na segunda vez, o programa chama output.send(). Esse método envia um objeto para o gerador. Neste exemplo, o objeto é uma string. A expressão yield na função geradora é avaliada como essa cadeia de caracteres, que é atribuída a value. Portanto, o gerador pode usar a string em seu código.
A segunda chamada para .send() envia um novo objeto para o gerador, que é atribuído à mesma variável value. O gerador gera um StopIteration após a última chamada print(), pois não há mais expressões yield.
Vamos dar uma olhada em outro exemplo usando .send(). O gerador a seguir exibe o saldo em uma conta, mas o saldo pode ser atualizado:
A função de gerador requer um saldo inicial quando chamada. O valor de balance pode mudar à medida que o gerador executa o código. Qualquer objeto enviado ao gerador usando .send() é atribuído a amount. Essa variável será None se o gerador gerar um valor sem nenhum objeto enviado a ele, ou conterá o objeto enviado usando .send().
O iterador do gerador current_balance começa com um saldo de US$ 100. O gerador é iniciado chamando next(), que começa a executar o código até que o primeiro valor seja gerado.
Depois que o gerador for iniciado, você poderá reiniciar a execução usando .send() em vez de next(). O gerador adiciona o valor enviado ao saldo. Se nenhum valor for enviado, por exemplo, se você chamar next() novamente, o gerador produzirá o saldo inalterado.
Obtendo diretamente de outro iterável
Os geradores Python também podem gerar valores diretamente de outro gerador ou iterável usando a sintaxe yield from. Vejamos um exemplo de uma função geradora que produz valores de uma lista aninhada:
defflatten(nested_list):for item in nested_list:ifisinstance(item,list):yieldfrom flatten(item)else:yield item
nested_list =[1,[2,3],[4,[5,6]],7]print(list(flatten(nested_list)))Tecnologia
A função geradora aceita uma lista, que pode incluir listas aninhadas dentro dela. O loop for percorre os itens da lista. Cada item da lista externa é um valor, nesse caso, um número inteiro, ou outra lista. Quando o item não é uma lista, o gerador produz o item.
No entanto, quando o item é uma lista, o gerador chama recursivamente a função geradora flatten() novamente com a lista interna como argumento. Isso cria outro iterador gerador, que usa a lista interna como sua fonte de dados. Se essa linha usasse uma expressão yield, o primeiro gerador produziria o segundo gerador. Em vez disso, ao usar yield from, o primeiro gerador produz valores do segundo gerador.
Resumo: yield vs. return
As definições de função com return e yield são semelhantes, mas seu comportamento é diferente. Vamos resumir as principais diferenças:
Função regular
Função do gerador
Palavra-chave
return (implícito se não for usado explicitamente)
rendimento
Chamado
Executa o código até que o retorno seja alcançado e, em seguida, retorna o valor final
Cria um iterador de gerador
Rescisão
Encerrado pelo comando return
Pausado por rendimento, pode ser retomado mais tarde
Valor de retorno
Objeto único (pode ser uma estrutura de dados)
Gerador de iterador
Expressão de rendimento
Não aplicável (cria uma declaração)
É avaliado como None ou como o valor enviado usando .send()
Casos de uso
Ideal para retornar um resultado final
Ideal para criar um fluxo de dados, especialmente sequências grandes ou infinitas
Conclusão
A palavra-chave yield do Python é usada em funções para definir uma função geradora. Quando chamadas, essas funções criam iteradores de geradores. Os geradores são um exemplo de avaliação preguiçosa em Python, em que as expressões são avaliadas quando o valor é necessário, em vez de quando você executa a expressão. Portanto, a expressão yield é útil para criar um fluxo de dados em que os valores são gerados sob demanda, sem a necessidade de armazená-los na memória.
As considerações de eficiência são importantes ao lidar com grandes conjuntos de dados que exigem muitas operações. Os iteradores geradores do Python são uma das principais ferramentas necessárias para que você manipule grandes quantidades de dados com eficiência.
Saiba tudo sobre a compreensão de dicionário do Python: como você pode usá-la para criar dicionários, substituir loops for (aninhados) ou funções lambda por map(), filter() e reduce(), ...!
Descubra como escrever funções em Python reutilizáveis e eficientes. Domine parâmetros, instruções de retorno e temas avançados como funções lambda. Organize melhor seu código com main() e outras boas práticas.
Aprenda Python para desenvolvimento de software, desde escrever funções até definir classes. Adquira as habilidades necessárias para dar o pontapé inicial na sua carreira como desenvolvedor!
As funções Python nem sempre têm uma declaração
return. As funções geradoras são funções que têm a palavra-chaveyieldem vez dereturn.Essas funções produzem iteradores de geradores, que são objetos que representam um fluxo de dados. Os elementos representados por um iterador são criados e cedidos somente quando necessário. Esse tipo de avaliação é geralmente chamado de avaliação preguiçosa.
Ao lidar com grandes conjuntos de dados, os geradores oferecem uma alternativa eficiente em termos de memória para o armazenamento de dados em listas, tuplas e outras estruturas de dados que exigem espaço na memória para cada um de seus elementos. As funções geradoras também podem criar iteradores infinitos, o que não é possível com estruturas avaliadas com ansiedade, como listas e tuplas.
Antes de começarmos, vamos recapitular as diferenças entre funções e geradores:
Recurso
Função
Gerador
Produção de valor
Retorna todos os valores de uma só vez
Produz valores um de cada vez, sob demanda
Execução
Executa completamente antes de retornar
Faz uma pausa depois de ceder, retoma quando o próximo valor é solicitado
Palavra-chave
retorno
rendimento
Uso da memória
Potencialmente alto, armazena toda a sequência na memória
Baixo, armazena apenas o valor e o estado atuais para o próximo
Iteração
Várias iterações são possíveis, mas requerem o armazenamento de toda a sequência
Projetado para iteração de passagem única, mais eficiente para sequências grandes ou infinitas
Usando a função Python
yieldpara criar funções de geradorO termo gerador em Python pode se referir a um iterador gerador ou a uma função geradora. Esses são objetos diferentes, mas relacionados em Python. Neste tutorial, os termos completos são usados com frequência para evitar confusão.
Primeiro, vamos explorar as funções do gerador. Uma função geradora é semelhante a uma função normal, mas contém a palavra-chave
yieldem vez dereturn.Quando um programa Python chama uma função geradora, ele cria um iterador gerador. Os iteradores produzem um valor sob demanda e pausam sua execução até que outro valor seja necessário. Vamos dar uma olhada em um exemplo para explicar esse conceito e demonstrar a diferença entre funções regulares e funções geradoras.
Usando uma função regular
Primeiro, vamos definir uma função regular, que contém uma instrução
return. Essa função aceita uma sequência de palavras e uma letra e retorna uma lista contendo o número de ocorrências da letra em cada palavra:A função gera uma lista contendo 1, 3 e 0, pois há um a em apple, três ocorrências de a em banana e nenhuma em cherry. A mesma função pode ser refatorada para usar compreensões de lista em vez de inicializar uma lista vazia e usar
.append():Essa função regular retorna uma lista contendo todos os resultados sempre que é chamada. No entanto, se a lista de palavras for grande, chamar essa função regular exige muito da memória, pois o programa cria e armazena uma nova lista do mesmo tamanho que a original. Se essa função for usada repetidamente em vários argumentos de entrada, ou se funções semelhantes estiverem realizando outras operações nos dados originais, a pressão na memória poderá aumentar rapidamente.
Usando uma função de gerador
Em vez disso, você pode usar uma função geradora:
A função inclui a palavra-chave
yieldem vez dereturn. Essa função de gerador retorna um objeto gerador quando chamada, que é atribuído aoutput. Esse objeto é um iterador. Ele não contém os dados que representam o número de ocorrências da letra em cada palavra. Em vez disso, o gerador criará e produzirá os valores quando necessário. Vamos buscar o primeiro valor desse iterador gerador:A função integrada
next()é uma maneira de obter o próximo valor de um iterador. Veremos outras maneiras mais adiante neste tutorial.O código na função geradora é executado até que o programa atinja a linha com a palavra-chave
yield. Neste exemplo, o loopforinicia sua primeira iteração e busca o primeiro elemento da listawords. O método de cadeia de caracteres.count()retorna um número inteiro, que, nesse caso, é 1, pois há uma ocorrência de a em apple. O gerador produz esse valor, que é retornado pornext(output).O gerador
outputinterrompe sua execução nesse ponto. Portanto, o gerador concluiu a primeira iteração do loopfore encontrou o número de ocorrências da letra a na primeira palavra da lista de palavras. Agora, ele está aguardando até que seja necessário novamente.Se o
next()incorporado for chamado novamente comoutputcomo argumento, o gerador retomará a execução a partir do ponto em que foi pausado:O gerador continua a partir da linha com
yieldna primeira iteração do loopfor. Como o loopfornão tem mais linhas de código, ele retorna ao topo do loop e busca o segundo elemento da listawords. O valor retornado por.count()é 3 nesse caso, e esse valor é gerado. O gerador faz uma nova pausa nesse ponto da execução.A terceira chamada para
next()retoma essa execução:A segunda iteração chega ao final do loop
for, que passa para a terceira iteração. O código avança para a linha comyieldnovamente, desta vez produzindo o inteiro 0, pois não há nenhuma ocorrência de a em cherry.O gerador faz uma nova pausa. O programa só determina o destino do gerador quando chamamos
next()uma quarta vez:A execução é retomada a partir do final da terceira iteração do loop
for. No entanto, o loop chegou ao fim de sua iteração, pois não há mais elementos na listawords. O gerador gera uma exceçãoStopIteration.Na maioria dos casos de uso, os elementos do gerador não são acessados diretamente usando
next(), mas por meio de outro processo de iteração. A exceçãoStopIterationsinaliza o fim do processo de iteração. Exploraremos isso mais detalhadamente na próxima seção deste tutorial.O Python tem outra maneira de criar iteradores geradores quando sua operação pode ser representada por uma única expressão, como no exemplo anterior. O iterador do gerador
outputpode ser criado usando uma _expressão do gerador_:A expressão entre parênteses atribuída a
outputé uma expressão geradora, que cria um iterador gerador semelhante ao produzido pela função geradorafind_letter_occurrences().Vamos concluir esta seção com outro exemplo de uma função geradora para destacar como a execução é pausada e retomada sempre que um elemento é necessário:
Essa função geradora não tem um loop. Em vez disso, ele contém três linhas que têm a palavra-chave
yield. O código cria um iterador geradorstatusquando você chama a função geradorashow_status(). Na primeira vez que o programa chamanext(status), o gerador inicia a execução. Ele imprime a string"Start"e faz uma pausa após a primeira expressãoyield. O gerador geraNone, pois não há nenhum objeto após a palavra-chaveyield.O programa imprime a cadeia de caracteres
"Middle"somente quandonext()é chamado uma segunda vez:O gerador faz uma pausa após a segunda expressão
yield. A terceira chamada paranext()imprime a string final,"End":O gerador faz uma pausa na expressão final
yield. Você abrirá uma exceçãoStopIterationna próxima vez que o programa solicitar um valor desse iterador gerador:Exploraremos mais maneiras de usar geradores na seção a seguir.
Trabalhando com iteradores de gerador
As funções de gerador criam iteradores de gerador, e os iteradores são iteráveis. Vamos analisar essa frase. Toda vez que o programa chama uma função geradora, ele cria um iterador. Como os iteradores são iteráveis, eles podem ser usados em
forloops e outros processos iterativos.Portanto, a função integrada
next()não é a única maneira de acessar elementos em um iterador. Esta seção explora outras formas de trabalhar com geradores.Usando o protocolo de iteração do Python com iteradores de gerador
Vamos revisitar uma função geradora de uma seção anterior deste tutorial:
Em vez de usar
next()várias vezes, essa versão do código usa o iterador do geradoroutputem um loopfor. Como os iteradores são iteráveis, eles podem ser usados emforloops. O loop busca itens no iterador do gerador até que não haja mais valores.Ao contrário das estruturas de dados, como listas e tuplas, um iterador só pode ser usado uma vez. O código não imprime os valores novamente se tentarmos executar o mesmo loop
foruma segunda vez:O iterador é exaurido pelo primeiro loop
for, portanto, não pode mais gerar valores. Se o gerador for necessário novamente depois de esgotado, devemos criar outro iterador de gerador a partir da função de gerador.Também é possível que você tenha vários iteradores de geradores em um programa ao mesmo tempo:
A função geradora
find_letter_occurrences()cria dois iteradores de gerador:first_outputesecond_output. Embora ambos os iteradores se refiram aos mesmos dados na listawords, eles progridem independentemente um do outro.Este exemplo obtém o primeiro valor de
first_outputusandonext(). O iterador do gerador produz 1 e faz uma pausa nesse ponto. O programa faz um loop emsecond_outputnext. Como esse gerador ainda não produziu nenhum valor, o loop percorre todos os valores produzidos pelo segundo iterador. Por fim, há outro loopforiterando porfirst_output. No entanto, esse iterador já produziu seu primeiro valor anteriormente no programa. O loop percorre os valores restantes nesse iterador.O loop
fornão é o único processo que pode ser usado para iterar por meio de iteradores de geradores:Nesses exemplos, o programa chama a função do gerador diretamente para criar e usar o iterador do gerador em vez de atribuí-lo a uma variável. No primeiro exemplo, o iterador é descompactado usando a notação em estrela. Esse processo se baseia no mesmo protocolo de iteração que o loop
for.No segundo exemplo, o iterador do gerador é passado para o built-in
sorted(), que requer um argumento iterável. Os geradores são iteráveis e, portanto, podem ser usados sempre que a iteração do Python ocorrer.Criação de iteradores infinitos
Um gerador produz um valor e faz uma pausa até que o próximo valor seja necessário. Toda vez que o código solicitar um valor de um iterador, o código na função geradora será executado até que a próxima expressão
yieldseja avaliada. Em todos os exemplos deste tutorial até agora, a função geradora tinha um número finito de expressõesyield. No entanto, é possível criar um gerador que produza um número infinito de valores usando um loopwhilena função geradora. No exemplo a seguir, o gerador produz uma cor aleatória da lista de cores passada para a função geradora:A função geradora
get_color()tem uma expressãoyieldem um loopwhile. Portanto, o código sempre encontrará outra expressãoyieldao procurar o próximo valor. O iterador geradoroutput_colorsproduz um número infinito de cores escolhidas aleatoriamente na lista de entrada. Esse gerador nunca se esgotará.Não é possível criar estruturas de dados infinitas, como listas e tuplas. Os geradores permitem que um programa crie iteráveis infinitos. Observe que se o iterador do gerador for usado diretamente em um loop
for, o loop será executado para sempre.Conceitos avançados de geradores
Os geradores têm casos de uso mais avançados em Python. Esta seção explorará alguns deles.
Envio de um objeto para o gerador
Os geradores também podem aceitar dados adicionais que podem ser usados durante a avaliação do código. A instrução que contém a palavra-chave
yieldé uma expressão que é avaliada como um valor. Esse valor pode ser atribuído a uma variável dentro da função geradora. Vamos começar com um exemplo básico para demonstrar esse conceito:A palavra-chave Python
yieldcria uma expressão que é avaliada como um valor. No entanto, o valor dessa expressão na função geradora não é o mesmo objeto gerado pelo gerador. Considere a primeira expressãoyield. O gerador produz o número inteiro 1. Portanto, o siteprint(next(output))exibe 1 na primeira vez em que é chamado e pausa a execução do gerador.No entanto, a expressão
yieldno gerador é avaliada como um objeto, que o código atribui ao nome da variávelvalue. Neste exemplo,yieldatribuiNoneavalue. Esse processo é repetido para a segunda ocorrência deyieldna função geradora. O objetivo da terceira chamadanext()é garantir que todo o código na função geradora seja executado.Vamos substituir a segunda e a terceira chamadas para
next()por.send()que é um método na classe do gerador:A função do gerador não foi alterada. O gerador é iniciado chamando
next(), e o código é executado até produzir o primeiro número inteiro, 1. Em vez de usarnext()na segunda vez, o programa chamaoutput.send(). Esse método envia um objeto para o gerador. Neste exemplo, o objeto é uma string. A expressãoyieldna função geradora é avaliada como essa cadeia de caracteres, que é atribuída avalue. Portanto, o gerador pode usar a string em seu código.A segunda chamada para
.send()envia um novo objeto para o gerador, que é atribuído à mesma variávelvalue. O gerador gera umStopIterationapós a última chamadaprint(), pois não há mais expressõesyield.Vamos dar uma olhada em outro exemplo usando
.send(). O gerador a seguir exibe o saldo em uma conta, mas o saldo pode ser atualizado:A função de gerador requer um saldo inicial quando chamada. O valor de
balancepode mudar à medida que o gerador executa o código. Qualquer objeto enviado ao gerador usando.send()é atribuído aamount. Essa variável seráNonese o gerador gerar um valor sem nenhum objeto enviado a ele, ou conterá o objeto enviado usando.send().O iterador do gerador
current_balancecomeça com um saldo de US$ 100. O gerador é iniciado chamandonext(), que começa a executar o código até que o primeiro valor seja gerado.Depois que o gerador for iniciado, você poderá reiniciar a execução usando
.send()em vez denext(). O gerador adiciona o valor enviado ao saldo. Se nenhum valor for enviado, por exemplo, se você chamarnext()novamente, o gerador produzirá o saldo inalterado.Obtendo diretamente de outro iterável
Os geradores Python também podem gerar valores diretamente de outro gerador ou iterável usando a sintaxe
yield from. Vejamos um exemplo de uma função geradora que produz valores de uma lista aninhada:A função geradora aceita uma lista, que pode incluir listas aninhadas dentro dela. O loop
forpercorre os itens da lista. Cada item da lista externa é um valor, nesse caso, um número inteiro, ou outra lista. Quando o item não é uma lista, o gerador produz o item.No entanto, quando o item é uma lista, o gerador chama recursivamente a função geradora
flatten()novamente com a lista interna como argumento. Isso cria outro iterador gerador, que usa a lista interna como sua fonte de dados. Se essa linha usasse uma expressãoyield, o primeiro gerador produziria o segundo gerador. Em vez disso, ao usaryield from, o primeiro gerador produz valores do segundo gerador.Resumo:
yieldvs.returnAs definições de função com
returneyieldsão semelhantes, mas seu comportamento é diferente. Vamos resumir as principais diferenças:Função regular
Função do gerador
Palavra-chave
return (implícito se não for usado explicitamente)
rendimento
Chamado
Executa o código até que o retorno seja alcançado e, em seguida, retorna o valor final
Cria um iterador de gerador
Rescisão
Encerrado pelo comando return
Pausado por rendimento, pode ser retomado mais tarde
Valor de retorno
Objeto único (pode ser uma estrutura de dados)
Gerador de iterador
Expressão de rendimento
Não aplicável (cria uma declaração)
É avaliado como None ou como o valor enviado usando .send()
Casos de uso
Ideal para retornar um resultado final
Ideal para criar um fluxo de dados, especialmente sequências grandes ou infinitas
Conclusão
A palavra-chave
yielddo Python é usada em funções para definir uma função geradora. Quando chamadas, essas funções criam iteradores de geradores. Os geradores são um exemplo de avaliação preguiçosa em Python, em que as expressões são avaliadas quando o valor é necessário, em vez de quando você executa a expressão. Portanto, a expressãoyieldé útil para criar um fluxo de dados em que os valores são gerados sob demanda, sem a necessidade de armazená-los na memória.As considerações de eficiência são importantes ao lidar com grandes conjuntos de dados que exigem muitas operações. Os iteradores geradores do Python são uma das principais ferramentas necessárias para que você manipule grandes quantidades de dados com eficiência.
Se você quiser saber mais sobre Python, confira este Programa de carreira para desenvolvedores Python.