Padrão Iterator em Python: Estrutura e Implementação via PyPattyrn

Fundamentos do Padrão Iterator

O padrão Iterator é um padrão de projeto comportamental essencial que permite a travessia sequencial de elementos de um objeto agregado sem expor sua representação interna. No ecossistema Python, esse conceito é nativo e onipresente, servindo como base para construções como loops for, expressões geradoras e compreensões de lista.

A adoção deste padrão traz benefícios arquitetônicos significativos:

  • Desacoplamento de responsabilidades: Separa a lógica de armazenamento de dados da lógica de navegação.
  • Polimorfismo de travessia: Permite que uma mesma coleção seja percorrida de diferentes maneiras utilizando iteradores distintos.
  • Avaliação preguiçosa (Lazy Evaluation): Computa e retorna elementos sob demanda, otimizando o consumo de memória em fluxos de dados massivos.

A biblioteca PyPattyrn fornece uma base sólida para este padrão, definindo as abstrações necessárias para que desenvolvedores construam seus próprios iteradores personalizados de forma padronizada.

Arquitetura Base do Iterador

A implementação do padrão no PyPattyrn baseia-se em duas abstrações principais. Para fins de clareza e adaptação, utilizaremos nomenclaturas que refletem diretamente o protocolo de iteração do Python.

Abstração da Coleção

A classe base define o contrato para qualquer objeto que possa ser iterado. Utilizando o módulo abc do Python, garantimos que as subclasses implementem o método de extração do próximo elemento.

from abc import ABC, abstractmethod

class DataCollection(ABC):
    """
    Define a interface para objetos que podem ser percorridos.
    """
    @abstractmethod
    def __next__(self):
        """
        Deve retornar o próximo item ou lançar StopIteration.
        """
        pass

Classe do Iterador

O iterador concreto encapsula a lógica de controle do estado da travessia, delegando a obtenção dos dados para a coleção alvo.

class DataIterator:
    """
    Implementa o protocolo de iteração delegando a busca à coleção.
    """
    def __init__(self, source: DataCollection):
        self.source = source

    def __iter__(self):
        return self

    def __next__(self):
        return self.source.__next__()

Construindo um Iterador Personalizado

Para demonstrar a aplicação prática, vamos criar uma sequência numérica que avança com um passo (step) definido, em vez de um simples contador unitário.

Passo 1: Definir a Coleção de Dados

class SteppedSequence(DataCollection):
    def __init__(self, start_val: int, limit_val: int, step_val: int):
        self.cursor = start_val
        self.limit = limit_val
        self.step = step_val

    def __next__(self):
        if self.cursor >= self.limit:
            raise StopIteration
        
        current_item = self.cursor
        self.cursor += self.step
        return current_item

Passo 2: Encapsular no Iterador

class SteppedIterator(DataIterator):
    def __init__(self):
        # Inicializa com uma sequência de 0 a 50, pulando de 10 em 10
        super().__init__(SteppedSequence(0, 50, 10))

Passo 3: Executar a Travessia

sequence_runner = SteppedIterator()
for value in sequence_runner:
    print(f"Processando item: {value}")
    # Saída: 0, 10, 20, 30, 40

Cenários de Aplicação Avançada

1. Processamento de Fluxos de Logs

Iteradores são ideais para ler arquivos de log gigantes linha por linha, evitendo o carregamento do arquivo inteiro na RAM.

class LogStream(DataCollection):
    def __init__(self, file_path: str):
        self.file_handler = open(file_path, 'r', encoding='utf-8')
    
    def __next__(self):
        raw_line = self.file_handler.readline()
        if not raw_line:
            self.file_handler.close()
            raise StopIteration
        return raw_line.strip()

# Consumo eficiente de memória
# for log_entry in DataIterator(LogStream("server_access.log")):
#     parse_and_store(log_entry)

2. Geração de Sequências Infinitas

Como a avaliação é preguiçosa, podemos modelar séries matemáticas infinitas, como potências de base 2.

class PowerOfTwoStream(DataCollection):
    def __init__(self):
        self.current_power = 1
    
    def __next__(self):
        result = self.current_power
        self.current_power *= 2
        return result

# Extraindo apenas os primeiros 5 elementos
# pow_iterator = DataIterator(PowerOfTwoStream())
# for _ in range(5):
#     print(next(pow_iterator))

3. Encadeamento de Múltiplas Fontes

É possível criar um iterador que consome múltiplas coleções sequencialmente, transparentemente para o cliente.

class ChainedDataIterator(DataIterator):
    def __init__(self, collections_list: list):
        self.active_streams = [DataIterator(c) for c in collections_list]
        self.current_stream_idx = 0
    
    def __next__(self):
        while self.current_stream_idx < len(self.active_streams):
            try:
                return next(self.active_streams[self.current_stream_idx])
            except StopIteration:
                self.current_stream_idx += 1
        raise StopIteration

# Uso:
# combined = ChainedDataIterator([SteppedSequence(0, 5, 1), SteppedSequence(10, 15, 1)])
# for item in combined: print(item)

Validação e Testes Unitários

Garantir que o protocolo de iteração esteja correto exige testes que validem tanto a extração de valores quanto o comportamento de exaustão (StopIteration).

import unittest

class TestSteppedIteratorBehavior(unittest.TestCase):
    
    def setUp(self):
        self.target_iterator = SteppedIterator()

    def test_sequence_extraction(self):
        expected_values = [0, 10, 20, 30, 40]
        extracted_values = [next(self.target_iterator) for _ in range(5)]
        self.assertListEqual(expected_values, extracted_values)

    def test_iteration_exhaustion(self):
        # Consome todos os elementos disponíveis
        list(self.target_iterator) 
        
        # A próxima chamada deve falhar
        with self.assertRaises(StopIteration):
            next(self.target_iterator)

    def test_native_loop_compatibility(self):
        self.target_iterator = SteppedIterator()
        count = 0
        for _ in self.target_iterator:
            count += 1
        self.assertEqual(count, 5)

Tags: Python design-patterns iterator-pattern pypattyrn object-oriented-programming

Publicado em 8-27 02:45