A generator é uma função ou expressão que retorna um tipo especial de iterador chamado iterador gerador.
Generator-iterators são preguiçosos: não armazenam seus values na memória, mas geram seus valores quando necessário.
Uma função geradora se parece com qualquer outra função, mas contém uma ou mais expressões yield.
Cada yield suspende a execução do código, salvando o estado atual de execução (incluindo todas as variáveis locais e as instruções try).
Quando o gerador é retomado, ele recupera o estado de onde foi suspenso, ao contrário das funções normais, que reiniciam a cada chamada.
Os geradores são construídos de forma parecida com outras funções de laço ou recursivas, mas exigem uma expressão yield, que vamos explorar em detalhes um pouco mais adiante.
Um exemplo é uma função que retorna os quadrados de uma determinada lista de números.
Do jeito que está escrita, toda a entrada precisa ser processada antes que qualquer valor possa ser retornado:
>>> def squares(list_of_numbers):
... squares = []
... for number in list_of_numbers:
... squares.append(number ** 2)
... return squares
Você pode converter essa função em um gerador assim:
>>> def squares_generator(list_of_numbers):
... for number in list_of_numbers:
... yield number ** 2
A lógica por trás disso é que você usa um gerador quando não precisa de todos os valores de uma vez.
Isso economiza memória e poder de processamento, já que só o valor no qual você está trabalhando no momento é calculado.
Geradores (tecnicamente, generator-iterators: veja a nota abaixo.) são um tipo de iterator e podem ser usados em qualquer lugar do Python onde se espera um iterator ou um iterable.
Isso inclui funções ou objetos que exigem um iterable/iterator como argumento.
Para se aprofundar, veja Como criar um iterador em Python.
Os iteradores geradores são um subconjunto especial dos iteradores.
Os Iterators são o mecanismo/protocolo que permite iterar sobre iteráveis.
Os iteradores geradores e os iteradores retornados pelos iterables comuns do Python agem de forma muito parecida, mas há algumas diferenças importantes a observar:
indexes.
Você não pode referenciar um valor anterior ou futuro usando adição ou subtração, e não pode usar notação de colchetes ([]) nem fatiamento.len(), pois não têm comprimento.generator-iterator infinito!Para usar o gerador squares_generator():
>>> squared_numbers = squares_generator([1, 2, 3, 4])
>>> for square in squared_numbers:
... print(square)
...
1
4
9
16
Os valores dentro de um gerador também podem ser produzidos/acessados por meio da função next().
next() chama o método __next__() de um objeto gerador, "avançando" ou avaliando o código do gerador até sua expressão yield, que então "produz" ou retorna o valor.
>>> squared_numbers = squares_generator([1, 2])
>>> next(squared_numbers)
1
>>> next(squared_numbers)
4
Quando um generator é totalmente consumido e não tem mais valores a retornar, ele lança um erro StopIteration.
>>> next(squared_numbers)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Os geradores são um subconjunto especial dos iteradores.
Os Iterators são o mecanismo/protocolo que permite iterar sobre iteráveis.
Os geradores e os iteradores retornados pelos iterables comuns do Python agem de forma muito parecida, mas há algumas diferenças importantes a observar:
Os geradores são unidirecionais; não há como "voltar" a um valor anterior.
Iterar sobre geradores consome os valores retornados; não há como reiniciar.
Os geradores (por serem avaliados de forma preguiçosa) não podem ser ordenados nem revertidos.
Os geradores não têm indexes, então você não pode referenciar um valor anterior ou futuro usando adição ou subtração.
Os geradores não podem ser usados com a função len().
Os geradores podem ser finitos ou infinitos; cuidado ao coletar todos os valores de um gerador infinito.
A expressão yield é muito parecida com a expressão return.
Ao contrário da expressão return, o yield entrega valores ao chamador em um ponto específico, suspendendo a avaliação/retorno de quaisquer valores adicionais até que sejam solicitados.
Quando o yield é avaliado, ele pausa a execução da função que o contém e retorna quaisquer valores da função naquele momento.
A função então permanece no escopo, e quando __next__() é chamado, a execução continua até que o yield seja encontrado novamente.
O uso de expressões yield é proibido fora de funções.
>>> def infinite_sequence():
... current_number = 0
... while True:
... yield current_number
... current_number += 1
>>> lets_try = infinite_sequence()
>>> lets_try.__next__()
0
>>> lets_try.__next__()
1
Os geradores são úteis em muitas aplicações.
Ao trabalhar com uma coleção grande, você pode não querer colocar todos os seus valores na memory.
Um gerador pode ser usado para trabalhar com dados maiores peça por peça, economizando memória e melhorando o desempenho.
Os geradores também são muito úteis quando um processo ou cálculo é complexo, caro ou infinito:
>>> def infinite_sequence():
... current_number = 0
... while True:
... yield current_number
... current_number += 1
Agora, sempre que __next__() for chamado no objeto infinite_sequence, ele retornará o número anterior + 1.