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.
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.
Gostou desse artigo? Ele faz parte do Drops de programação, um conjunto de posts mais curtos voltados para fundamentos falando sobre alguns aspectos da linguagem Python e de programação em geral. Você pode ler mais desses artigos buscando a tag “drops” aqui no site.
Até a próxima!