Você sabia que é possível criar uma sequência infinita sem ter problemas de memória e ainda consumir essa sequência da forma que você quiser? À medida que seu entendimento da linguagem Python aumenta, você começa a explorar os módulos presentes na biblioteca padrão do Python. Hoje veremos um método específico do módulo itertools
, o islice
, que é excelente para nosso propósito. E usaremos a sequência de Fibonacci como exemplo.
Tópicos
Conhecendo o método
Sempre que ouvir falar de alguma funcionalidade de sua linguagem de programação favorita (ou da linguagem que paga suas contas…) busque conhecer a documentação. A documentação do módulo itertools
o descreve como:
Esse módulo implementa diversos blocos de instruções com iteradores, inspirados por construções de APL, Haskell, e SML. Cada uma foi adequadamente reformulada para Python. Esse módulo padroniza um conjunto central de ferramentas rápidas e de uso eficiente da memória, que podem ser utilizadas sozinhas ou combinadas. Juntas, eles formam uma “álgebra de iteradores” tornando possível construir ferramentas sucintas e eficientes em Python puro. (…)
Veja que a documentação cita iteradores. Logo, caso não tenha muito familiaridade com o conceito, recomendo ler esse artigo aqui do site onde explico detalhadamente do que se trata. Depois volte para este artigo aqui :-).
Quanto ao método islice
, a documentação o descreve como:
itertools.islice(iterable, stop)
ouitertools.islice(iterable, start, stop[, step])
. Cria um iterador que retorna elementos específicos de um iterável. (…)
Uma descrição bastante sucinta. Vamos começar importando o método:
from itertools import islice
Criando nossa sequência infinita
Agora vamos criar nossa sequência infinita. Se você já leu o artigo sobre geradores sabe que é perfeitamente possível criar tais sequências. Basta criar um gerador e gerar os valores sob demanda:
def pares_positivos():
valor = 0
while True:
yield valor
valor += 2
Perceba que o gerador vai gerar inteiros positivos a partir do zero sempre que solicitado. Vamos lembrar rapidamente o comportamento de um gerador como esse. Primeiro, vendo que o Python o reconhece como gerador:
pares_positivos()
<generator object pares_positivos at 0x7fac9d1deeb0>
Vamos associá-lo com uma variável para poder consumí-lo via chamadas next
:
p = pares_positivos()
next(p)
0
next(p)
2
next(p)
4
E assim poderíamos seguir indefinidamente. Vamos agora entender o que faz o islice
.
Consumindo a sequência de forma controlada
Vamos dar uma olhada na descrição do método com mais detalhes:
help(islice)
Help on class islice in module itertools: class islice(builtins.object) | islice(iterable, stop) --> islice object | islice(iterable, start, stop[, step]) --> islice object | | Return an iterator whose next() method returns selected values from an | iterable. If start is specified, will skip all preceding elements; | otherwise, start defaults to zero. Step defaults to one. If | specified as another value, step determines how many values are | skipped between successive calls. Works like a slice() on a list | but returns an iterator. | | Methods defined here: | | __getattribute__(self, name, /) | Return getattr(self, name). | | __iter__(self, /) | Implement iter(self). | | __next__(self, /) | Implement next(self). | | __reduce__(...) | Return state information for pickling. | | __setstate__(...) | Set state information for unpickling. | | ---------------------------------------------------------------------- | Static methods defined here: | | __new__(*args, **kwargs) from builtins.type | Create and return a new object. See help(type) for accurate signature.
Perceba que há duas assinaturas. A primeira é islice(iterable, stop)
. Vamos então passar nosso iterável (o gerador) e um valor de parada, por exemplo, 10:
islice(p, 10)
<itertools.islice at 0x7fac9c16ee50>
Observe que realmente é um iterador. Vamos então consumí-lo, passando-o para uma tupla:
tuple(islice(p, 10))
(6, 8, 10, 12, 14, 16, 18, 20, 22, 24)
Veja que interessante. Já havíamos começado a consumir nosso gerador, chegando até o número 4. Agora foram gerador os próximos 10 inteiros sob demanda. Não houve necessidade de chamadas next
nem de construir algum tipo de loop for
ou algo do tipo. O islice
cuida disso. E podemos solicitar os próximos 10 inteiros da mesma forma:
tuple(islice(p, 10))
(26, 28, 30, 32, 34, 36, 38, 40, 42, 44)
Temos agora perfeito controle sobre o consumo dessa sequência.
A própria função geradora poderia ser passada como argumento, por exemplo:
tuple(islice(pares_positivos(), 20))
(0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38)
Foram gerados os 20 primeiros inteiros positivos pares diretamente.
A famosa sequência de Fibonacci
Convenhamos que a sequência de números pares positivos não é lá muito prática. Mas a sequência de Fibonacci é bem famosa e tem suas aplicações. Vamos utilizá-la e aprender um pouco mais sobre islice
. Primeiro, vamos implementar um gerador para a sequência de Fibonacci:
def fibonacci():
a, b = 0, 1
while True:
yield a
b = a + b
yield b
a = a + b
Vamos ver o início da sequência para verificar nossa implementação:
f = fibonacci()
f
<generator object fibonacci at 0x7fac9c16a6d0>
next(f)
0
next(f)
1
next(f)
1
next(f)
2
OK, funcionando. Podemos, novamente usar o islice
como fizemos anteriormente, vendo os 10 primeiros números da sequência:
tuple(islice(fibonacci(), 10))
(0, 1, 1, 2, 3, 5, 8, 13, 21, 34)
Podemos, também, verificar como usar a segunda assinatura do método: islice(iterable, start, stop[, step])
.
Por exemplo, podemos verificar quais os números entre as posições 5 e 10:
tuple(islice(fibonacci(), 5, 10))
(5, 8, 13, 21, 34)
Mas cuidado com a interpretação. O 5 se refere ao índice, e índices em Python começam em zero. Assim, veja que na realidade foram apresentados valores a partir do sexto na sequência (compare com o resultado da célula anterior). E o último índice é excluso, similar à um slice de listas em Python.
O último parâmetro é o step, também similar ao de listas:
tuple(islice(fibonacci(), 5, 10, 2))
(5, 13, 34)
Caso queira alguma posição em específico, basta chamar um next
nesta posição e passar None
como stop:
next(islice(fibonacci(), 5, None))
5
Conclusão
Mais uma etapa no entendimento e uso de geradores. Espero que os artigos estejam tendo uma boa conexão entre si e agregando conhecimento. Esse artigo faz parte da tag “Python Drops” aqui do site, de artigos curtos sobre fundamentos da linguagem. Compartilhe em suas redes e siga o projeto Ciência Programada para sempre estar atualizado. Até a próxima.