Patrón de Iterador en Python

Imagina que tienes un frasco lleno de caramelos de varios colores. Quieres probar los caramelos uno a uno, pero sin tener que vaciar todos los caramelos de una vez. En ese momento, metes la mano en el frasco y sacas un caramelo a la vez: ese proceso es la iteración.

En programación,el patrón de iteradores un patrón de diseño que proporciona una forma de acceder secuencialmente a los elementos de un objeto de colección sin necesidad de exponer la representación interna de dicho objeto.

Conceptos clave

El patrón de iterador consta de dos componentes principales:

  1. Iterador (Iterator): responsable de definir la interfaz para acceder y recorrer los elementos
  2. Objeto iterable (Iterable): proporciona el método para crear iteradores

En Python, el patrón de iterador está profundamente integrado en las características principales del lenguaje, lo que hace que nuestra programación sea más elegante e intuitiva.


¿Por qué se necesitan iteradores?

Limitaciones del enfoque tradicional

Veamos primero un ejemplo sin usar iteradores:

Ejemplo

# Una clase sencilla de colección de libros
class BookCollection:
    def __init__(self):
        self.books = ["Introducción a Python", "Introducción a los algoritmos", "Patrones de diseño", "Estructuras de datos"]
   
    def get_books(self):
        return self.books

# Usar esta colección
collection = BookCollection()
books = collection.get_books()

# Iterar sobre los libros - expone detalles de implementación interna
for i in range(len(books)):
    print(books[i])

Análisis del problema:

  • El código del cliente necesita conocer la estructura interna de la colección (lista).
  • Si la implementación interna de la colección cambia (por ejemplo, de lista a diccionario), todo el código del cliente debe modificarse.
  • La lógica de recorrido está fuertemente acoplada a la implementación de la colección.

Ventajas del iterador

Ejemplo

# Usando iteradores
for book in collection:
    print(book)

Beneficios que aporta el iterador:

  • Encapsulamiento: oculta la implementación interna de la colección.
  • Interfaz unificada: diferentes tipos de colecciones pueden usar la misma forma de recorrido.
  • Flexibilidad: se puede cambiar fácilmente la implementación de la colección.
  • Soporte de múltiples recorridos: se pueden realizar varias operaciones de recorrido simultáneamente.

El protocolo del iterador en Python

Python implementa el protocolo del iterador mediante dos métodos especiales:

__iter__()Método

  • Devuelve un objeto iterador.
  • Los objetos iterables deben implementar este método.

__next__()Método

  • Devuelve el siguiente elemento de la secuencia.
  • Cuando no hay más elementos, lanza unaStopIterationexcepción.

Entendamos el mecanismo de funcionamiento del iterador mediante un diagrama de flujo:


Crear un iterador personalizado

Método 1: Implementar el iterador usando una clase

Vamos a crear un iterador personalizado de libros:

Ejemplo

class BookIterator:
    """Clase iterador de libros"""
   
    def __init__(self, books):
        self.books = books
        self.index = 0
   
    def __iter__(self):
        """Devuelve el propio iterador"""
        return self
   
    def __next__(self):
        """Devuelve el siguiente libro, o lanza StopIteration si no hay más libros"""
        if self.index < len(self.books):
            book = self.books[self.index]
            self.index += 1
            return book
        else:
            raise StopIteration

class BookCollection:
    """Clase de colección de libros iterable"""
   
    def __init__(self):
        self.books = ["Introducción a Python", "Introducción a los algoritmos", "Patrones de diseño", "Estructuras de datos"]
   
    def __iter__(self):
        """Devuelve una instancia de iterador"""
        return BookIterator(self.books)

# Usar un iterador personalizado
collection = BookCollection()
for book in collection:
    print(f"Leyendo actualmente: {book}")

Resultado de salida:

正在阅读: Python入门
正在阅读: 算法导论
正在阅读: 设计模式
正在阅读: 数据结构

Método 2: Usar funciones generadoras

Python ofrece una forma más concisa: las funciones generadoras.

Ejemplo

class BookCollection:
    def __init__(self):
        self.books = ["Introducción a los algoritmos", "Patrones de diseño", "Estructuras de datos", """Crear un iterador con una función generadora"""]
   
    def __iter__(self):
        # La forma de uso es exactamente la misma
        for book in self.books:
            yield book

"Leyendo: {book}"
collection = BookCollection()
for book in collection:
    print(fVentajas de los generadores:)

Código más conciso

  • Manejo automático del estado guardado
  • Mejor rendimiento
  • Escenarios de aplicación práctica del iterador

Escenarios de aplicación práctica del iterador

Escenario 1: Lectura de datos paginados

Ejemplo

class PaginatedData:
    # Simular la lectura de una página de datos de la base de datos
   
    def __init__(self, total_items, page_size=3):
        self.total_items = total_items
        self.page_size = page_size
        self.current_page = 0
   
    def __iter__(self):
        return self
   
    def __next__(self):
        start = self.current_page * self.page_size
        end = start + self.page_size
       
        if start >= self.total_items:
            raise StopIteration
       
        # Usar el iterador de paginación
        page_data = list(range(start, min(end, self.total_items)))
        self.current_page += 1
       
        return page_data

# En total 10 datos, 3 por página
paginator = PaginatedData(10, 3)  "Datos de la página {page_num}: {page_data}"
for page_num, page_data in enumerate(paginator, 1):
    print(fResultado de salida:)

Escenario 2: Generación de secuencias infinitas

第1页数据: [0, 1, 2]
第2页数据: [3, 4, 5]
第3页数据: [6, 7, 8]
第4页数据: [9]

Escenario 2: Generación de secuencias infinitas

Ejemplo

class FibonacciIterator:
    # Generar la secuencia de Fibonacci
   
    def __init__(self, max_count=10):
        self.max_count = max_count
        self.count = 0
        self.a, self.b = 0, 1
   
    def __iter__(self):
        return self
   
    def __next__(self):
        if self.count >= self.max_count:
            raise StopIteration
       
        result = self.a
        self.a, self.b = self.b, self.a + self.b
        self.count += 1
        return result

"Secuencia de Fibonacci:"
fib = FibonacciIterator(8)
print(Resultado de salida:, list(fib))

Herramientas integradas para iteradores

斐波那契数列: [0, 1, 1, 2, 3, 5, 8, 13]

Herramientas integradas para iteradores

Función

iter()ynext()Función

Ejemplo

numbers = [1, 2, 3, 4, 5]

# Salida: 1
iterator = iter(numbers)

print(next(iterator))  # Salida: 2
print(next(iterator))  # Salida: 3
print(next(iterator))  Función

enumerate()Función

Ejemplo

fruits = ['apple', 'banana', 'orange']

for index, fruit in enumerate(fruits):
    print(fFunción)

zip()Función

Ejemplo

names = ['Alice', 'Bob', 'Charlie']
scores = [85, 92, 78]

for name, score in zip(names, scores):
    print(fIterador vs Objeto iterable)

Iterador vs Objeto iterable

Característica

Objeto iterable (Iterable) Iterador (Iterator) Definición
Implementa el método__iter__()Implementa el método__iter__()y__next__()Uso
Puede ser iterado Ejecuta realmente la operación de iteración Estado
Generalmente sin estado Mantiene el estado de iteración (posición actual, etc.) Ejemplo
Listas, tuplas, diccionarios, cadenas Objeto devuelto por iter()Diagrama de relación

Diagrama de relación

Ejemplo

graph TD
B -->|llamar a next repetidamente| C[elemento por elemento]
C --> D[hasta StopIteration]
Mejores prácticas y errores comunes

Mejores prácticas y errores comunes

Mejores prácticas

  1. Ejemplo

Ejemplo

# No recomendado: implementar manualmente la clase iterador
def countdown(n):
    while n > 0:
        yield n
        n -= 1

Aprovechar las funciones integradas
  1. Ejemplo

Ejemplo

# expresión generadora
squares = (x*x for x in range(10))  # No recomendado

# ... implementación extensa
class SquareIterator:
    Errores comunes

Errores comunes

Ejemplo

Ejemplo

numbers = [1, 2, 3]

# Error: la lista en sí no es un iterador
try:
    next(numbers)  # TypeError: 'list' object is not an iterator
except TypeError as e:
    print(f"Error: {e}")

# Correcto: primero obtener el iterador
iterator = iter(numbers)
print(next(iterator))  # Salida: 1

Error 2: continuar usando el iterador después de agotarse

Ejemplo

numbers = [1, 2, 3]
iterator = iter(numbers)

print(list(iterator))  # Salida: [1, 2, 3]
print(list(iterator))  # Salida: [] - ¡El iterador está agotado!

Ejercicios prácticos

Ejercicio 1: Crear un iterador personalizado

Crear unaCountdownclase que implemente un iterador que cuente hacia atrás desde un número especificado hasta 1:

Ejemplo

class Countdown:
    def __init__(self, start):
        self.start = start
   
    def __iter__(self):
        # Tu código aquí
        current = self.start
        while current > 0:
            yield current
            current -= 1

# Prueba tu implementación
for num in Countdown(5):
    print(num)  # Debería mostrar: 5, 4, 3, 2, 1

Ejercicio 2: Iterador de líneas de archivo

Crear un iterador que pueda leer un archivo línea por línea y añada el número de línea al inicio de cada línea:

Ejemplo

class NumberedLines:
    def __init__(self, filename):
        self.filename = filename
   
    def __iter__(self):
        with open(self.filename, 'r', encoding='utf-8') as file:
            for line_num, line in enumerate(file, 1):
                yield f"{line_num}: {line.rstrip()}"

Resumen

El patrón iterador es un concepto extremadamente importante en la programación Python; nos permite:

  • Acceso unificado: recorrer diferentes estructuras de datos de la misma manera
  • Encapsulación de la implementación: ocultar la estructura interna de la colección
  • Evaluación perezosa: generar datos solo cuando sea necesario, ahorrando memoria
  • Uso combinado: se integra perfectamente con características como generadores, comprensiones, etc.

Recuerda este simple principio:Cualquier objeto que implemente__iter__()el método es iterable, y cualquier objeto que implemente__next__()el método es iterador.

Otras extensiones