Ge

Geradores em Python

1 exercício

Sobre Geradores

Um generator é uma função ou expressão que devolve um tipo especial de iterador chamado iterador gerador. Os Generator-iterators são preguiçosos: não guardam os seus values em memória, mas geram os valores quando é preciso.

Uma função geradora é parecida com qualquer outra função, mas contém uma ou mais expressões yield. Cada yield suspende a execução do código e guarda o estado de execução atual (incluindo todas as variáveis locais e instruções try). Quando o gerador continua, retoma o estado a partir do ponto de suspensão, ao contrário das funções normais, que se reiniciam a cada chamada.

Construir um gerador

Os geradores constroem-se de forma muito semelhante a outras funções com ciclos ou recursivas, mas precisam de uma expressão yield, que vamos explorar com mais detalhe um pouco mais à frente.

Um exemplo é uma função que devolve os quadrados de uma dada lista de números.
Tal como está escrita, todos os valores de entrada têm de ser processados antes de se poder devolver algum valor:

>>> def squares(list_of_numbers):
...     squares = []
...     for number in list_of_numbers:
...         squares.append(number ** 2)
...     return squares

Podes converter essa função num gerador assim:

>>> def squares_generator(list_of_numbers):
...     for number in list_of_numbers:
...         yield number ** 2

A razão para isto é que usas um gerador quando não precisas de todos os valores ao mesmo tempo.

Isto poupa memória e poder de processamento, uma vez que só é calculado o valor com que estás a trabalhar neste momento.

Utilizar um gerador

Os geradores (tecnicamente, generator-iterators, vê a nota abaixo.) são um tipo de iterator e podem ser usados em qualquer lugar de Python onde se espere um iterator ou um iterable. Isto inclui funções ou objetos que exigem um iterable/iterator como argumento. Para um mergulho mais profundo, vê How to Make an Iterator in Python.

Note

Os iteradores geradores são um subconjunto especial de iteradores. Os Iterators são o mecanismo/protocolo que permite iterar sobre iteráveis. Os iteradores geradores e os iteradores devolvidos pelos iterables comuns de Python comportam-se de forma muito semelhante, mas há algumas diferenças importantes a ter em conta:

  • Têm avaliação preguiçosa; a iteração é unidirecional e não há forma de "recuar" para um valor anterior.
  • São consumidos ao iterar sobre os valores devolvidos; não há reinício nem gravação em memória.
  • Não são ordenáveis nem podem ser invertidos.
  • Não são tipos de sequência e não têm indexes. Não podes referenciar um valor anterior ou futuro usando adição ou subtração nem podes usar a notação de parênteses retos ([]) ou fatiamento.
  • Não podem ser usados com a função len(), pois não têm comprimento.
  • Podem ser finitos ou infinitos, por isso tem cuidado ao recolher todos os valores de um 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 de um gerador também podem ser produzidos/acedidos através 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 depois "cede" ou devolve 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 para devolver, lança um erro StopIteration.

>>> next(squared_numbers)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Diferença entre iteráveis e geradores

Os geradores são um subconjunto especial de iteradores. Os Iterators são o mecanismo/protocolo que permite iterar sobre iteráveis. Os geradores e os iteradores devolvidos pelos iterables comuns de Python comportam-se de forma muito semelhante, mas há algumas diferenças importantes a ter em conta:

  • Os geradores são unidirecionais; não há forma de "recuar" para um valor anterior.

  • Iterar sobre geradores consome os valores devolvidos; não há reinício.

  • Os geradores (por terem avaliação preguiçosa) não são ordenáveis nem podem ser invertidos.

  • Os geradores não têm indexes, por isso não podes 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, tem cuidado ao recolher todos os valores de um gerador infinito.

A expressão yield

A expressão yield é muito semelhante à expressão return.

Ao contrário da expressão return, o yield cede valores ao chamador num ponto específico, suspendendo a avaliação/devolução de quaisquer valores adicionais até que sejam pedidos.

Quando o yield é avaliado, faz uma pausa na execução da função que o contém e devolve os valores da função nesse momento.

A função mantém-se em âmbito e, quando o __next__() é chamado, a execução retoma até se encontrar o yield novamente.

Note

A utilização de expressões yield é proibida 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

Porquê usar geradores?

Os geradores são úteis em muitas aplicações.

Quando trabalhas com uma coleção grande, podes não querer colocar todos os seus valores em memory. Um gerador pode ser usado para trabalhar com dados maiores peça a peça, poupando memória e melhorando o desempenho.

Os geradores também são muito úteis quando um processo ou cálculo é complexo, dispendioso ou infinito:

>>> def infinite_sequence():
...     current_number = 0
...     while True:
...         yield current_number
...         current_number += 1

Agora, sempre que o __next__() é chamado no objeto infinite_sequence, devolve o número anterior + 1.

Editar via GitHub A ligação abre numa nova janela ou separador

Aprende Geradores

A prática está bloqueada

Desbloqueia mais 2 exercícios para praticares Geradores