Em Python, os geradores são uma ferramenta poderosa para criar iteradores de forma concisa, utilizando a palavra-chave yield. Esta abordagem simplifica a construção de sequências preguiçosas, que produzem valores sob demanda.
Para ilustrar, definimos uma função geradora básica:
def criar_gerador():
print("Executando o gerador")
yield 'u'
yield 'v'
yield 'w'
gen = criar_gerador()
print(type(gen)) # Saída: <class 'generator'>
Este gerador pode ser iterado diretamente em um laço for:
for caractere in criar_gerador():
print(caractere)
# Saída:
# Executando o gerador
# u
# v
# w
Os geradores mantêm seu estado entre as chamadas de yield. Considere uma implementação similar a uma classe contadora:
def gerador_intervalo(valor_inicial, valor_final):
atual = valor_inicial
while atual <= valor_final:
yield atual
atual += 1
for num in gerador_intervalo(3, 7):
print(num, end=' ')
# Saída: 3 4 5 6 7
Ao inspecionar um objeto gerador com dir(), métodos como __iter__ e __next__ estarão presentes, confirmando seu protocolo de iteração.
Uma aplicação comum é o processamento eficeinte de grandes volumes de daddos, como na varredura de diretórios com os.walk(), que atua como um gerador para economizar memória.
Geradores podem produzir sequências infinitas:
def gerador_infinito(inicio=0):
valor = inicio
while True:
yield valor
valor += 1
contador = gerador_infinito(2)
for _ in range(5):
print(next(contador), end=' ')
# Saída: 2 3 4 5 6
Uma limitação importante é que gerdaores não são reutilizáveis após o consumo. No entanto, é possível criar objetos iteráveis usando classes que implementam o método __iter__:
class IntervaloIteravel:
def __init__(self, minimo, maximo):
self.minimo = minimo
self.maximo = maximo
def __iter__(self):
corrente = self.minimo
while corrente <= self.maximo:
yield corrente
corrente += 1
intervalo = IntervaloIteravel(4, 9)
for val in intervalo:
print(val, end=' ')
# Saída: 4 5 6 7 8 9
# A iteração pode ser repetida, pois __iter__ retorna um novo gerador a cada chamada
Diferentemente, para transformar uma classe em um iterador completo, deve-se implementar tanto __iter__ quanto __next__:
from collections.abc import Iterator
class SequenciaLimitada:
def __init__(self, comeco, limite):
self.atual = comeco
self.limite = limite
def __iter__(self):
return self
def __next__(self):
if self.atual > self.limite:
raise StopIteration
valor = self.atual
self.atual += 1
return valor
seq = SequenciaLimitada(10, 13)
print(isinstance(seq, Iterator)) # Saída: True
for elemento in seq:
print(elemento, end=' ')
# Saída: 10 11 12 13