Pular para o conteúdo principal
InicioTutoriaisPython

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.
Actualizado 29 de jul. de 2024

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:

def find_letter_occurrences(words, letter):
    output = []
    for word in words:
        output.append(word.count(letter))
    return output
print(
    find_letter_occurrences(["apple", "banana", "cherry"], "a")
)
[1, 3, 0]

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():

def find_letter_occurrences(words, letter):
    return [word.count(letter) for word in words]

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:

def find_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)
<generator object find_letter_occurrences at 0x102935e00>

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:

print(next(output))
1

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:

print(next(output))
3

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:

print(next(output))
0

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:

print(next(output))
Traceback (most recent call last):
  ...
StopIteration

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))
1
3
0
Traceback (most recent call last):
  ...
StopIteration

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:

def show_status():
    print("Start")
    yield
    print("Middle")
    yield
    print("End")
    yield
status = show_status()
next(status)
Start

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:

next(status)
Middle

O gerador faz uma pausa após a segunda expressão yield. A terceira chamada para next() imprime a string final, "End":

next(status)
End

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:

next(status)
Traceback (most recent call last):
  ...
StopIteration

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:

def find_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)
1
3
0

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:

def find_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)
First attempt:
1
3
0
Second attempt:

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:

 
def find_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)
First value of first_output:
1
Values of second_output:
1
3
0
Remaining values of first_output:
3
0

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:

print(*find_letter_occurrences(words, letter))
print(sorted(find_letter_occurrences(words, letter)))
1 3 0
[0, 1, 3]

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
def get_color(colors):
    while True:
        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 _ in range(10):
    print(next(output_colors))
First two colors:
green
red
Next 10 colors using a 'for' loop:
blue
green
green
green
red
red
red
blue
green
red

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:

def generator_function():
    value = yield 1
    print(f"The yield expression evaluates to: {value}")
    value = yield 2
    print(f"The yield expression evaluates to: {value}")
output = generator_function()
print(next(output))
print(next(output))
print(next(output))
1
The yield expression evaluates to: None
2
The yield expression evaluates to: None
Traceback (most recent call last):
  ...
StopIteration

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:

def generator_function():
    value = yield 1
    print(f"The yield expression evaluates to: {value}")
    value = yield 2
    print(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"))
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):
  ...
StopIteration

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:

def get_balance(start_balance):
    balance = start_balance
    while True:
        amount = yield balance
        if amount is not None:
            balance += amount
current_balance = get_balance(100)
print(next(current_balance))
print(current_balance.send(10))
print(current_balance.send(-20))
print(next(current_balance))
100
110
90
90

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:

def flatten(nested_list):
    for item in nested_list:
        if isinstance(item, list):
            yield from flatten(item)
        else:
            yield item
nested_list = [1, [2, 3], [4, [5, 6]], 7]
print(list(flatten(nested_list)))
[1, 2, 3, 4, 5, 6, 7]

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.

Se você quiser saber mais sobre Python, confira este Programa de carreira para desenvolvedores Python.

Temas

Aprenda Python com estes cursos!

Course

Introduction to Python for Developers

3 hr
13.1K
Master the fundamentals of programming in Python. No prior knowledge required!
See DetailsRight Arrow
Start Course
Ver maisRight Arrow
Relacionado

tutorial

Tutorial de iteradores e geradores Python

Explore a diferença entre Iteradores e Geradores do Python e saiba quais são os melhores para usar em várias situações.

Kurtis Pykes

10 min

tutorial

Tutorial de compreensão de dicionário Python

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(), ...!
Sejal Jaiswal's photo

Sejal Jaiswal

14 min

tutorial

Tutorial de funções Python

Um tutorial sobre funções em Python que aborda como escrever funções, como chamá-las e muito mais!
Karlijn Willems's photo

Karlijn Willems

14 min

tutorial

Função Python Print()

Saiba como você pode aproveitar os recursos de uma simples função Python Print de várias maneiras com a ajuda de exemplos.
Aditya Sharma's photo

Aditya Sharma

10 min

tutorial

Tutorial de lambda em Python

Aprenda uma maneira mais rápida de escrever funções em tempo real com as funções lambda.
DataCamp Team's photo

DataCamp Team

3 min

tutorial

Escopo das variáveis em Python

Saiba o que são as miras telescópicas variáveis e familiarize-se com a regra "LEGB". Você também lidará com cenários em que verá as palavras-chave globais e não locais em ação.
Sejal Jaiswal's photo

Sejal Jaiswal

9 min

See MoreSee More