Para iniciar, vamos fazer uma implementação simples do que seria o range do python.
1 def my_range(start, stop=None, step=1):
2 if stop is None:
3 stop = start
4 start = 0
5
6 ret = []
7
8 while start < stop:
9 ret.append(start)
10 start += step
11
12 return ret
13
14
15 for i in my_range(100):
16 print(i)
O problema dessa implementação é que ele gera um list desnecessário, vamos supor que queremos gerar um my_range(10000000), ele irá retornar um list com 10000000 de células. É um desperdício de memória.
Iterator
Um iterator, segundo a documentação do Python é "um objeto que representa um fluxo de dados. Repetidas chamadas ao método next do objeto iterator retorna o item seguinte. Quando não há mais dados é lançado a exception StopIteration.". Para um objeto ser iterator é necessário que ele contenha o método __iter__, grande parte das implementações desse método retorna self. Vejamos a implementação do my_range com iterator. 1 class my_xrange(object):
2 def __init__(self, start, stop=None, step=1):
3 if stop is None:
4 stop = start
5 start = 0
6
7 self.start = start
8 self.stop = stop
9 self.step = step
10 self.current = start
11
12 def __iter__(self):
13 return self
14
15 # Python 3
16 def __next__(self):
17 if self.current < self.stop:
18 ret = self.current
19 self.current += 1
20 return ret
21 raise StopIteration()
22
23 # Python 2 / Compatibilizando
24 def next(self):
25 return self.__next__()
26
27
28 for i in my_xrange(100):
29 print(i)
Beleza! Resolvemos o problema de memória, mas... É muito código pra pouca coisa! A solução está nos generators!
Generators
Segundo a documentação do Python generators "permitem que você declare uma função que se comporta como um iterador, ou seja, ele pode ser usado em um loop". No lugar de return, usamos yield. 1 def my_xrange(start, stop=None, step=1):
2 if stop is None:
3 stop = start
4 start = 0
5
6 while start < stop:
7 yield start
8 start += step
9
10
11 for i in my_xrange(100):
12 print(i)
O código ficou muito mais limpo. Quando a palavra chave yield está dentro de uma função, esta função se torna um iterator. Para demonstrar o funcionamento do iterator coloquei uns prints no código acima, para entendermos o seu funcionamento.
1 def my_xrange(start, stop=None, step=1):
2 if stop is None:
3 stop = start
4 start = 0
5
6 print('my_xrange iniciado.')
7 while start < stop:
8 print('código bloqueado no yield')
9 yield start
10 print('código liberado após iteração')
11 start += step
12 print('acabei')
13
14
15 the_range = my_xrange(2)
16 print('Vou mostrar o primeiro item do the_range')
17 print(the_range.__next__())
18 print('----------------------------------------')
19
20 # para o python2 use
21 # print(the_range.next())
22
23 print('Vou iterar o the_range')
24 print('----------------------------------------')
25 print(the_range.__next__())
26 print('----------------------------------------')
27 print(the_range.__next__())
O output será o seguinte:
Vou mostrar o primeiro item do the_range my_xrange iniciado. código bloqueado no yield 0 ---------------------------------------- Vou iterar o the_range ---------------------------------------- código liberado após iteração código bloqueado no yield 1 ---------------------------------------- código liberado após iteração acabei Traceback (most recent call last): File "my_xrange_stoped.py", line 27, inprint(the_range.__next__()) StopIteration
Repare que quando chamamos o método my_xrange(2), o print que é exibido no começo do corpo da função 'my_xrange iniciado.' (linha 6) não foi exibido no output.
Isso porque quando chamamos o método, o Python não chama a função, ele gera um iterator. A função só é executada quando o método __next__ (Python3) ou next (Python2) é chamado. Quando o __next__ é chamado ele libera a execução da função, se a execução chegar a algum yield o valor do yield é retornado, senão, é lançado o StopIteration, como no nosso exemplo nosso loop ia até dois, na terceira chamada ao método __next__ foi lançado o exception StopIterator.
Nenhum comentário:
Postar um comentário