/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

  1. # Iteradores
  2. 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__`.
  3. - Um objeto iterável é um objeto que implementa `__iter__`, que deve retornar um objeto iterador.
  4. - 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.
  5. ## Introdução
  6. **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.
  7. 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.
  8. 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**.
  9. ```python
  10. lista = [1,2,3,4]
  11. iterador = iter(lista)
  12. print(next(iterador)) # 1
  13. print(next(iterador)) # 2
  14. print(iterador.__next__()) # 3
  15. print(iterador.__next__()) # 4
  16. next(iterador) # Sem mais itens para iterar, erro StopIteration ocorre
  17. ```
  18. 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.
  19. ```python
  20. for elemento in lista:
  21. print(lista)
  22. # 1
  23. # 2
  24. # 3
  25. # 4
  26. ```
  27. 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:
  28. ```python
  29. for elemento in iteravel:
  30. # faça algo com o elemento
  31. ```
  32. É na verdade:
  33. ```python
  34. # cria um objeto iterador do iteravel
  35. objeto_iteravel = iter(iteravel)
  36. # Loop infinito
  37. while True:
  38. try:
  39. # obtém o novo item
  40. elemento = next(objeto_iteravel)
  41. # faz algo com o elemento
  42. except StopIteration:
  43. # Se o StopIteration ocorrer, break no loop
  44. break
  45. ```
  46. 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.
  47. ## Criando um Iterador
  48. 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.
  49. ```python
  50. class Numeros:
  51. def __iter__(self):
  52. self.x = 1
  53. return self
  54. def __next__(self):
  55. y = self.x
  56. self.x += 1
  57. return y
  58. n = Numeros()
  59. iterador = iter(n)
  60. print(next(iterador))
  61. print(next(iterador))
  62. print(next(iterador))
  63. print(next(iterador))
  64. print(next(iterador))
  65. # 1
  66. # 2
  67. # 3
  68. # 4
  69. # 5
  70. ```
  71. ## StopIteration
  72. 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.
  73. ```python
  74. class Numeros:
  75. def __iter__(self):
  76. self.x = 1
  77. return self
  78. def __next__(self):
  79. if self.x <= 6:
  80. y = self.x
  81. self.x +=1
  82. return y
  83. else:
  84. raise StopIteration
  85. n = Numeros()
  86. iterador = iter(n)
  87. for elemento in iterador:
  88. print(elemento)
  89. # 1
  90. # 2
  91. # 3
  92. # 4
  93. # 5
  94. # 6
  95. ```
  96. ## Iterador Aleatório
  97. Vejamos um iterador que nos retorna sequências de comprimento aleatórios de **1**'s.
  98. ```python
  99. import random
  100. class RandomIterable:
  101. def __iter__(self):
  102. return self
  103. def __next__(self):
  104. if random.choice(["go", "go", "stop"]) == "stop":
  105. raise StopIteration # finaliza o iterador
  106. return 1
  107. for x in RandomIterable():
  108. print(x)
  109. # 1
  110. # 1
  111. # 1
  112. ```
  113. Esses foram os iteradores, conceito importante e muito presente em nossos estudos, central na linguagem Python, vamos então continuar **iterando** nosso conhecimento.