Geradores
As
funções geralmente seguem o seguinte fluxo:
- processar;
- retornar valores;
- encerrar;
Geradores funcionam parecido, eles lembram o estado do processamento entre as chamadas, ficando em memória e quando ativados retornam o próximo item.
Os
geradores apresentam várias vantagens em relação às funções convencionais:
- Lazy Evaluation: geradores só são processados quando é realmente necessário, por isso, economizam recursos de processamento e memória;
- Reduzem a necessidade da criação de listas;
- Permitem trabalhar com sequências ilimitadas de elementos;
Vamos a um exemplo para entender melhor quando e porque usar yield.
def numbers_func(max_n):
lst = []
for number in range(max_n + 1):
lst.append(number)
return lst
A função
numbers_func criada acima, cria uma lista chamada
lst com
[0, 1, 2, 3, 4, …, max_n] até chegar no
max_n.
Ela cria essa lista, aloca na memória e guarda toda a lista e seu conteúdo.
Ocupa espaço e usa recursos de hardware para isso.
Não há problema se
max_n for um número pequeno, mas, se for algo absurdamente grande tipo: (
3654 * 9872 ** 23), seu computador provavelmente irá
travar.
Então, nesses casos de um
max_n gigantesco, o melhor é usar
gerador ao invés de
função.
Vamos criar um gerador chamado numbers_yield:
def numbers_yield(max_n):
for n in range(max_n + 1):
yield n
Tente usar o gerador numbers com o número gigante:
numbers_yield(623 * 10 ** 21).
O computador não irá travar!
Ele só vai calcular o primeiro elemento da sequência quando precisar dele e vai entregar o
0 e vai
parar.
Não processa mais nada, não aloca nada em memória até o código solicitar o próximo número.
Aí então ele esquece do primeiro e te entrega o segundo e assim vai, ele vai entregando um por vez, o terceiro, depois o quarto, o quinto e assim por diante.
O gerador só vai calcular alguma coisa a cada
next(), que é a função chamada internamente se você, por exemplo, passar um gerador para um
for.
O next() também pode ser usado manualmente:
my_generator = numbers_yield(5)
next(my_generator)
next(my_generator)
next(my_generator)
Faça o teste acima e veja que o primeiro
next devolve
0, o segundo
1 e o terceiro
2.
O
range() que é nativo do Python 3 já é um gerador em si.
Geradores são muito úteis e não gastam muita memória, mas, existem algumas limitações, por exemplo, você não consegue usar dois
for no mesmo gerador diretamente, geradores só avançam na sequência, não retornam nunca ao começo dela.
Vamos a mais um exemplo para finalizar a aula.
Queremos uma lista com todos os anos desde o ano 1 DC até 5000 DC, é fácil criar usando compreensão de listas (
List Comprehensions ) ou como no caso abaixo, podemos criar um gerador e ir acessando cada ano dinamicamente com a função
next():
years_generator = (year for year in range(1, 5001))
Podemos não saber em que ano vamos começar nossa contagem nem em que ano devemos terminá-la, então vamos criar uma função geradora onde podemos passar o ano de inicio e ano final e é aí que entra o yield.
Basicamente o yield funciona como return de uma função, mas nesses casos ele irá manter o estado da função.
Vamos criar um
módulo que gere anos a partir e até os anos passados para ele, coloque o conteúdo abaixo em um arquivo
.py e salve dando o nome
years_generator (
years_generator.py ):
def years_generator(start = 1, end = 5000):
for year in range(start, end + 1):
yield year
Agora importe o módulo
gerador_anos e chame ele passando o ano inicial e o ano final:
import years_generator
for year in years_generator.years_generator(1900, 1910):
print(year)
Saída:
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
Valeu pessoal, nos vemos na próxima aula. 🖖
Se gostarem do conteúdo dêem um joinha 👍 na página do Código Fluente no
Facebook
Esse é o link do código fluente no Pinterest
Meus links de afiliados:
Obrigado, até e bons estudos. ;)