Geradores em Python
Este artigo explica os geradores em Python.
YouTube Video
Geradores em Python
Visão geral
Os geradores em Python são um tipo de iterador e um recurso poderoso para realizar processamentos repetitivos de forma eficiente. Eles permitem escrever códigos eficientes em memória ao lidar com grandes volumes de dados.
O que é um gerador?
Um gerador em Python é uma função especial que produz um valor de cada vez, definida usando a palavra-chave yield
. Sua característica é pausar a execução enquanto mantém seu estado e pode ser retomada posteriormente.
Noções básicas do yield
yield
é uma palavra-chave que retorna um valor e pausa a execução da função ao mesmo tempo.
1def simple_generator():
2 yield 1
3 yield 2
4 yield 3
5
6gen = simple_generator()
7
8print(next(gen)) # 1
9print(next(gen)) # 2
10print(next(gen)) # 3
- Quando chamada, esta função retorna um objeto gerador que fornece valores um por um.
- Se você chamar
next()
quando não houver próximo valor, ocorrerá um erroStopIteration
.
next()
e StopIteration
1def simple_generator():
2 yield 1
3 yield 2
4 yield 3
5
6gen = simple_generator()
7
8try:
9 while True:
10 value = next(gen)
11 print(value)
12except StopIteration:
13 print("Finished")
- Ao tratar explicitamente o erro
StopIteration
desta forma, você pode detectar quando um gerador terminou.
send(value)
Chamar send(value)
retoma o gerador e envia value
para a posição da expressão yield
. O valor enviado pode ser recebido do lado do gerador como valor de retorno da expressão yield
. Na primeira chamada, não é possível enviar nada além de None
com send(value)
, portanto, você deve usar next()
ou send(None)
.
1def gen():
2 x = yield 1
3 print(f"x = {x}")
4 y = yield 2
5 print(f"y = {y}")
6
7g = gen()
8print(next(g)) # -> 1 (value from yield 1)
9print(g.send(10)) # -> x = 10, 2 (value from yield 2)
10print(g.send(20)) # -> y = 20, StopIteration occurs
- Com
send(10)
, oyield
do gerador se torna uma expressão que retorna 10, e 10 é atribuído ax
.
throw()
Chamar throw
retoma o gerador e lança uma exceção na posição do yield
pausado. Você pode tratar a exceção dentro do gerador para continuar o processamento. Se a exceção não for capturada, ela se propaga para fora e o gerador é encerrado.
1def gen():
2 try:
3 yield 1
4 except ValueError as e:
5 print(f"Caught: {e}")
6 yield "recovered"
7
8g = gen()
9print(next(g)) # -> 1
10print(g.throw(ValueError("boom"))) # -> Caught: boom, "recovered"
- Neste código,
throw
é chamado para injetar uma exceção no gerador. Do lado do gerador, a exceção é tratada erecovered
é retornado.
close()
Chamar close()
encerra o gerador. Dentro do gerador, você pode executar a limpeza usando finally
. Chamar next()
ou send()
após chamar close()
levanta um erro StopIteration
.
1def gen():
2 try:
3 yield 1
4 finally:
5 print("Cleaning up...")
6
7g = gen()
8print(next(g)) # -> 1
9g.close() # -> Cleaning up...
- Este código mostra que chamar
close()
encerra o gerador e dispara o processo de limpeza emfinally
.
yield from
yield from
é uma sintaxe usada para delegar a um subgerador. É uma maneira simples de chamar outro gerador dentro de um gerador e passar todos os seus valores para o escopo externo.
1def sub_gen():
2 yield 1
3 yield 2
4
5def main_gen():
6 yield from sub_gen()
7 yield 3
8
9print(list(main_gen())) # -> [1, 2, 3]
- Este código delega todos os valores do subgerador ao gerador externo usando
yield from
e então produz3
.
Relação com iteradores
Geradores implementam internamente __iter__()
e __next__()
, tornando-os um tipo de iterador. Portanto, eles são totalmente compatíveis com operações iteráveis como loops for
.
Integração com loops for
Em Python, um loop for
usa internamente o next()
para recuperar valores automaticamente.
1def simple_generator():
2 yield 1
3 yield 2
4 yield 3
5
6for value in simple_generator():
7 print(value)
Dessa forma, o tratamento de StopIteration
também é automático.
Criando geradores infinitos
1def count_up(start=0):
2 while True:
3 yield start
4 start += 1
5
6counter = count_up()
7print(next(counter)) # 0
8print(next(counter)) # 1
É possível criar laços infinitos, mas deve-se ter cuidado ao utilizá-los.
Expressões de gerador
Expressões de gerador, escritas com parênteses, permitem definir geradores com sintaxe semelhante às compreensões de listas.
1# List comprehension (generates the entire list at once)
2squares_list = [x**2 for x in range(5)]
3print(squares_list)
4
5# Generator expression
6squares_gen = (x**2 for x in range(5))
7for square in squares_gen:
8 print(square)
Ao contrário das compreensões de listas, elas não carregam todos os elementos na memória de uma só vez, tornando-se mais eficientes em termos de memória.
Tratamento de erros em geradores
Exceções podem ocorrer dentro de um gerador. Nesses casos, você usa o try-except
como em um código Python normal.
1def safe_divide_generator(numbers, divisor):
2 """Yields results of dividing numbers by a given divisor safely."""
3 for number in numbers:
4 try:
5 yield number / divisor # Attempt to divide and yield result.
6 except ZeroDivisionError:
7 yield float('inf') # Return infinity if division by zero occurs.
8
9# Example usage
10numbers = [10, 20, 30]
11gen = safe_divide_generator(numbers, 0) # Create generator with divisor as 0.
12for value in gen:
13 print(value) # Output: inf, inf, inf
Neste exemplo, o tratamento adequado de erros é realizado no caso de uma divisão por zero.
Rastreamento de pilha de um gerador
Se uma exceção ocorrer dentro do gerador, ela será lançada quando o gerador for retomado.
1def error_generator():
2 """A generator that yields values and raises an error."""
3 yield 1
4 raise ValueError("An error occurred") # Raise a ValueError intentionally.
5 yield 2
6
7gen = error_generator()
8print(next(gen)) # Output: 1 (first value yielded)
9try:
10 print(next(gen)) # Attempt to get the next value, which raises an error
11except ValueError as e:
12 print(e) # Output: An error occurred (exception message is printed)
- Este gerador retorna
1
primeiro. O erro lançado ao retomar é capturado e exibido como uma mensagem de erro.
Exemplos de uso de geradores
Lendo um arquivo linha por linha (adequado para arquivos grandes)
1def read_large_file(filepath):
2 with open(filepath, 'r') as f:
3 for line in f:
4 yield line.strip()
- Esta função lê um arquivo de texto linha por linha usando um iterador, remove os espaços em branco de cada linha e retorna como um gerador, permitindo que arquivos grandes sejam processados com baixo uso de memória.
Gerador para a sequência de Fibonacci
1def fibonacci(limit):
2 a, b = 0, 1
3 while a < limit:
4 yield a
5 a, b = b, a + b
6
7for n in fibonacci(100):
8 print(n)
- Este código usa um gerador para gerar sequencialmente números de Fibonacci menores que o limite superior e os imprime usando um laço
for
.
Casos de uso
Os geradores também podem ser usados nos seguintes cenários.
- Processamento sequencial de arquivos CSV ou de log grandes
- Paginação de API
- Processamento de dados em streaming (por exemplo, Kafka, dispositivos IoT)
Resumo
Conceito | Ponto chave |
---|---|
yield |
Pausa e retorna um valor |
Função geradora | Uma função que contém yield e retorna um iterador quando chamada |
Vantagens | Eficiente em memória e ideal para processar grandes conjuntos de dados |
Expressão de gerador | Permite uma sintaxe concisa como (x for x in iterable) |
Ao usar geradores, você pode processar grandes conjuntos de dados de forma eficiente, economizando memória e mantendo seu código conciso.
Você pode acompanhar o artigo acima usando o Visual Studio Code em nosso canal do YouTube. Por favor, confira também o canal do YouTube.