Padrão de projeto - Iterator

Bruna Ferreira - Jul 11 '22 - - Dev Community

O Iterator é um padrão de projeto comportamental que permite percorrer em elementos de uma coleção sem precisar saber como isso está acontecendo debaixo dos panos, independente de ser lista, pilha, árvore, etc.


Estrutura de dados

Dentre as estruturas citadas acima, vamos utilizar uma árvore como exemplo:

Árvores com setas indicando o tipo de acesso, em profundidade e em largura

Como podemos ver na imagem acima, a árvore pode ser acessada de diferentes maneiras, de acordo com o solicitado pelo cliente, podendo ter acesso em profundidade ou em largura, além da possibilidade de implementação de outros tipos de acesso aos elementos.

O Iterator é um padrão que extrai essas diferentes formas de percorrer uma árvore para um objeto chamado iterável, segue a imagem:

Arvores e UML contendo os métodos de busca em profundidade e em largura


Exemplo simples:
Implementar um mecanismo de busca, onde o cliente vai utilizar o método getBusca() sem saber com qual algoritmo o método foi desenvolvido - Bubble Sort, Merge Sort, Quick Sort...


Quando usar?

Esse padrão pode ser utilizado quando a complexidade da estrutura de dados não é interessante para o cliente. O acesso aos elementos é fornecido através de métodos simples, encapsulando os detalhes do algoritmo, trazendo mais proteção às coleções, as quais não serão manipuladas diretamente.

Outra vantagem de definir a responsabilidade de percorrer a estrutura de dados para o iterador, tirando ela de dentro da regra de negócio, se dá no tempo que os algoritmos não triviais levam para buscar um elemento. Também pelo fato de que o objeto iterador tem seu próprio estado de iteração, sendo possível mais de um objeto percorrer a mesma coleção em paralelo. Assim o código do cliente se torna mais limpo e focado em seus objetivos.


Iterator no Python

O python possui um módulo Iterator e Iterable, para criar os iteradores e os objetos iteráveis.

from collections.abc import Iterator, Iterable
Enter fullscreen mode Exit fullscreen mode

Este módulo implementa os métodos __iter()__ e __next()__, os quais são necessários para um objeto ser iterável no Python.

import random

class RandomIterable:

def __iter__(self):
    return self
def __next__(self):
    if random.choice(["go", "go", "stop"]) == "stop":
        raise StopIteration
        # Sinaliza o fim da iteração
    return 1
Enter fullscreen mode Exit fullscreen mode

Criando o iterator.py

Em um arquivo chamado iterator.py, criamos a classe InsertOrder, a qual vai definir a ordem que os elementos de uma coleção serão percorridos - implementamos a função __next()__ aqui.

class InsertOrder(Iterator):
_position: int = None
_reverse: bool = False

def __init__(self, collection: Iterable,
            reverse: bool = False) -> None:
    self._collection = collection
    self._reverse = reverse
    self._position = -1 if reverse else 0

def __next__(self):
    try:
        value = self._collection[self._position]
        self._position += -1 if self._reverse else 1
    except IndexError:
        raise StopIteration()
    return value
Enter fullscreen mode Exit fullscreen mode
  • classe InsertOrder: possui atributos de posição do elemento e indicação se a coleção deve ser iterada na ordem normal ou reversa
  • função __init__: construtor da classe
  • função __next__: itera o objeto até não ter mais posições de índices, caindo na except, onde finaliza a iteração, aqui também verificamos se a busca vai ser normal ou reversa

No mesmo arquivo, criamos a classe WordsCollection, essa definirá um objeto iteravel, onde será implementada a segunda função necessária do Iterator, a __iter()__.

class WordsCollection(Iterable):
    def __init__(self, collection: list = []) -> None:
        self._collection = collection

    def __iter__(self) -> InsertOrder:
        return InsertOrder(self._collection)

    def get_reverse(self) -> InsertOrder:
        return InsertOrder(self._collection, True)

    def outras_formas(self) -> InsertOrder:
        # desenvolver outras formas de percorrer a coleção
        ...
        return InsertOrder(self.colletion)

    def add_item(self, item):
        self._collection.append(item)
Enter fullscreen mode Exit fullscreen mode
  • classe WordsCollection: recebe a estrutura de dados, aqui usamos uma lista para facilitar o entendimento
  • função __init__: construtor da classe, ele vai receber a coleção para iterar
  • função __iter__: invoca a classe InsertOrder, passa a coleção como parâmetro e retorna um iterador
  • função get_reverse: invoca a classe InsertOrder, passa a coleção e a sinalização de ordem reversa como parâmetros e retorna um iterador
  • função outras_formas: um exemplo criado sem implementação, podendo criar diferentes regras de busca na mesma coleção, retornando também um iterator
  • função add_item: adiciona elementos à uma coleção

Criando o cliente.py

O cliente é quem vai utilizar o iterador, sem precisar saber sobre suas regras de busca, apenas passando a coleção e indicando o método.

def instance_collection(elements: list[str]) -> WordsCollection:
    collection = WordsCollection()
    for element in elements:
        collection.add_item(element)

    return collection
Enter fullscreen mode Exit fullscreen mode
  • função instance_collection: instancia um objeto iterável, nesse caso uma lista de strings e retorna essa coleção

Executando o cliente.py

if __name__ == '__main__':
    elements = [
        'IFSC',
        'Programação',
        'Laboratório',
        'Professor',
        'Aluno'
        ]

    collection = instance_collection(elements)
Enter fullscreen mode Exit fullscreen mode
  • variável elements: definimos uma lista de elementos
  • variável collection: chama a função para instanciar um objeto iterável, passando como parametro elements

Iterando pela collection de forma normal:

for e in collection.__iter__():
    print(e)
Enter fullscreen mode Exit fullscreen mode

Saída:

IFSC
Programação
Laboratório
Professor
Aluno
Enter fullscreen mode Exit fullscreen mode

Iterando pela collection de forma reversa:

for e in collection.get_reverse():
    print(e)
Enter fullscreen mode Exit fullscreen mode

Saída:

Aluno
Professor
Laboratório
Programação
IFSC
Enter fullscreen mode Exit fullscreen mode

Considerações

Com algumas adaptações no iterator.py, podemos fazer a iteração de diversas coleções e estruturas, sem a necessidade de alterar o código cliente.py, pois esse apenas consumirá o iterador.


Conhecendo mais sobre o módulo collections.abc e iterator do Python.

Pessoas que compartilham conhecimentos na comunidade: vcwild

Principal referência para desenvolvimento deste artigo.

Repositório com o código completo.

Enjoy!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .