/Capitulos/26.Iteradores.md
https://github.com/the-akira/Python-Iluminado · Markdown · 149 lines · 113 code · 36 blank · 0 comment · 0 complexity · 876bb895c74a162a8631cbc16808d6bf MD5 · raw file
- # Iteradores
- Iteradores são objetos que podem ser iterados. Nesse guia vamos estudá-los e aprender como eles funcionam construindo um iterador usando os métodos `__iter__` e `__next__`.
- - Um objeto iterável é um objeto que implementa `__iter__`, que deve retornar um objeto iterador.
- - Um iterador é um objeto que implementa **next**, que deve retornar o próximo elemento do objeto iterável que o retornou e gerar uma exceção **StopIteration** quando não houver mais elementos disponíveis.
- ## Introdução
- **Iteradores** sempre estiveram conosco em Python, eles estão implementados nos **for loops**, **geradores**, etc, porém estão "escondidos". Um iterador é simplesmente um objeto que podemos iterar nele, ou seja, um objeto que retornará dados, um elemento de cada vez.
- O objeto iterador implementa dois métodos especiais `__iter__()` e `__next__()`, que são conhecidos como o protocolo do iterador. Um objeto é chamado de iterável se formos capazes de obter um iterador dele, estruturas como **lista**, **tupla** e **string** são iteráveis. A função **iter()** (que chama o método `__iter__`) retorna um iterador delas.
- Vamos então aos experimentos para entendermos melhor os iteradores, usaremos a função **next()** para iterar manualmente através de todos os itens de um iterador. Quando chegarmos ao fim e não houver mais dados para retorno, ocorrerá um erro **StopIteration**.
- ```python
- lista = [1,2,3,4]
- iterador = iter(lista)
- print(next(iterador)) # 1
- print(next(iterador)) # 2
- print(iterador.__next__()) # 3
- print(iterador.__next__()) # 4
- next(iterador) # Sem mais itens para iterar, erro StopIteration ocorre
- ```
- Uma forma melhor e mais simples de iterar automaticamente seria usando um for loop. Usando ele, nós podemos iterar sob qualquer objeto que retorne um iterador, por exemplo uma lista, string, arquivo, etc.
- ```python
- for elemento in lista:
- print(lista)
- # 1
- # 2
- # 3
- # 4
- ```
- Vimos que o **for** foi capaz de iterar automaticamente através da **lista**, agora vamos analisar como ele faz essa mágica internamente no Python:
- ```python
- for elemento in iteravel:
- # faça algo com o elemento
- ```
- É na verdade:
- ```python
- # cria um objeto iterador do iteravel
- objeto_iteravel = iter(iteravel)
- # Loop infinito
- while True:
- try:
- # obtém o novo item
- elemento = next(objeto_iteravel)
- # faz algo com o elemento
- except StopIteration:
- # Se o StopIteration ocorrer, break no loop
- break
- ```
- Veja que internamente o **for loop** cria um objeto iterador **objeto_iteravel** chamando o método **iter()** nele, e que na verdade ele é um **while loop**. Dentro desse while loop ele chama **next()** para pegar o próximo elemento e executar o for loop com esse valor, assim que todos os itens forem percorridos, StopIteration é acionado que é capturado internamente e o loop acaba, perceba que qualquer outra exceção irá passar.
- ## Criando um Iterador
- Para criar um **objeto**/**classe** como um iterador nós precisamos implementar os métodos `__iter__()` e `__next__()` no nosso objeto. Como aprendemos no nosso capítulo de Classes e Objetos, todas as classes possuem uma função chamada `__init__()`, que possibilita a inicialização quando o objeto é criado. O método `__iter__()` age similar, pode executar operações (inicializações, etc), mas sempre deve retornar o objeto iterador. O método `__next__()` também possibilita operações e deve retornar o próximo item na sequência.
- ```python
- class Numeros:
- def __iter__(self):
- self.x = 1
- return self
- def __next__(self):
- y = self.x
- self.x += 1
- return y
- n = Numeros()
- iterador = iter(n)
- print(next(iterador))
- print(next(iterador))
- print(next(iterador))
- print(next(iterador))
- print(next(iterador))
- # 1
- # 2
- # 3
- # 4
- # 5
- ```
- ## StopIteration
- O exemplo acima pode ser executado infinitamente, especialmente se aplicarmos um **for loop**. Para previnirmos que a iteração ocorra eternamente, podemos usar o comando **StopIteration** no método `__next__()`, nós podemos adicionar a condição de término para que o erro seja disparado caso a iteração ultrapasse a condição.
- ```python
- class Numeros:
- def __iter__(self):
- self.x = 1
- return self
- def __next__(self):
- if self.x <= 6:
- y = self.x
- self.x +=1
- return y
- else:
- raise StopIteration
- n = Numeros()
- iterador = iter(n)
- for elemento in iterador:
- print(elemento)
- # 1
- # 2
- # 3
- # 4
- # 5
- # 6
- ```
- ## Iterador Aleatório
- Vejamos um iterador que nos retorna sequências de comprimento aleatórios de **1**'s.
- ```python
- import random
- class RandomIterable:
- def __iter__(self):
- return self
- def __next__(self):
- if random.choice(["go", "go", "stop"]) == "stop":
- raise StopIteration # finaliza o iterador
- return 1
- for x in RandomIterable():
- print(x)
- # 1
- # 1
- # 1
- ```
- Esses foram os iteradores, conceito importante e muito presente em nossos estudos, central na linguagem Python, vamos então continuar **iterando** nosso conhecimento.