Skip to main content

Iterator pattern en Python

⏱ Dedicación recomendada: 0 minutos
Esto considera el contenido visible y relevante, e ignora texto colapsado o marcado como opcional.


r8vnhill/

Cuando diseñamos una biblioteca en Python, uno de los retos comunes es ofrecer formas elegantes y controladas de recorrer estructuras de datos personalizadas, como catálogos, colecciones, buffers o secuencias generadas dinámicamente. Para esto, el patrón iterador es una herramienta fundamental: nos permite separar la lógica de iteración de la estructura interna de los datos, y exponer una interfaz clara y predecible para quien usa la biblioteca.

Python ofrece soporte nativo para este patrón a través de su protocolo de iteración, basado en los métodos __iter__() y __next__(). A diferencia de otros lenguajes como Kotlin, donde se requiere implementar interfaces explícitas, en Python basta con que un objeto “se comporte como un iterador” para que pueda ser utilizado en bucles for, funciones como list() o herramientas como zip().

Duck typing

En Python, se sigue el principio de "duck typing", que se basa en la idea de que "si camina como un pato y suena como un pato, entonces es un pato". Esto significa que Python no requiere que los objetos hereden de una clase específica para ser iterables, sino que simplemente deben implementar los métodos necesarios para comportarse como un iterador.

Esto se traduce en APIs más idiomáticas, integradas con el lenguaje, y que siguen el principio de diseño de Python: simple es mejor que complejo.

En esta lección aprenderemos a implementar el patrón iterador de manera explícita, utilizando clases que siguen el protocolo, para crear una colección personalizada con un comportamiento de recorrido controlado. Veremos también qué ventajas nos brinda este enfoque frente a exponer directamente las listas internas, y cómo esto encaja en el contexto del diseño de bibliotecas reutilizables.

Implementación del patrón iterator en Python

En Python, un objeto es iterable si implementa el método __iter__() que devuelve un iterador. Un iterador, a su vez, debe implementar el método __next__(), que devuelve el siguiente elemento de la colección y lanza la excepción StopIteration cuando se terminan los elementos.

Ejemplo de implementación

Veamos un ejemplo similar al caso de Kotlin, en el que creamos una colección de libros y definimos un iterador para recorrerlos.

1. Clase Book y la colección Library

Primero, creamos una clase Book para representar los libros, y una clase Library que será iterable.

collections/iterator/library.py
class Book:
pass


class Library:
__books: list

def __iter__(self) -> 'LibraryIterator':
return LibraryIterator(*self.__books)
¿Qué acabamos de hacer?
  • La clase Book representa un libro con un título y un autor.
  • La clase Library contiene una colección de libros y devuelve un iterador para recorrerlos.
  • El método __iter__() de Library devuelve una instancia de LibraryIterator que recorrerá los libros.

2. Implementación del Iterador

Ahora implementamos la clase LibraryIterator que seguirá el protocolo de iteradores en Python, es decir, implementará los métodos __iter__() y __next__().

collections/iterator/library.py
class LibraryIterator:
def __next__(self) -> Book:
if self.__index < len(self.__books):
book = self.__books[self.__index]
self.__index += 1
return book
raise StopIteration()

def __iter__(self) -> 'LibraryIterator':
return self
¿Qué acabamos de hacer?
  • [2-7]: El método __next__() devuelve el siguiente libro de la colección y actualiza la posición interna del iterador.
  • [9-10]: El método __iter__() simplemente devuelve el iterador, que es el propio objeto.

3. Uso del Iterador

El iterador se puede utilizar directamente en un bucle for gracias a que Python implementa el protocolo de iteración de manera automática:

collections/iterator/library.py
if __name__ == '__main__':
LIBRARY = Library(
Book("Red Dragon", "Thomas Harris"),
Book("At the Mountains of Madness", "H.P. Lovecraft"),
Book("The Fellowship of the Ring", "J.R.R. Tolkien")
)

for BOOK in LIBRARY:
print(f"{BOOK.title} by {BOOK.author}")
# Output:
# Red Dragon by Thomas Harris
# At the Mountains of Madness by H.P. Lovecraft
# The Fellowship of the Ring by J.R.R. Tolkien

Comparación final

CaracterísticaKotlinPython
ImplementaciónUsa la interfaz Iterable e Iterator para implementar iteradores.Implementa el patrón mediante métodos __iter__() y __next__().
FlexibilidadRequiere que las clases implementen explícitamente las interfaces de iteración.Utiliza el principio de "duck typing", permitiendo que cualquier objeto que implemente los métodos necesarios sea iterable.
Manejo de fin de iteraciónUtiliza hasNext() para comprobar si hay más elementos antes de llamar a next().Usa la excepción StopIteration para indicar que no hay más elementos.
Bucle de iteraciónEl bucle for se basa en iterator() para realizar la iteración.El bucle for maneja automáticamente la iteración utilizando el protocolo de iteración.

Beneficios y limitaciones

Beneficios

  • Flexibilidad en la implementación: El uso de "duck typing" permite que cualquier objeto que implemente los métodos __iter__() y __next__() sea iterable, lo que brinda una gran flexibilidad en la creación de iteradores personalizados sin necesidad de heredar de clases específicas.
  • Simplicidad en la iteración: La integración del patrón iterador en el bucle for de Python permite una sintaxis limpia y simple para recorrer colecciones, mejorando la legibilidad del código.
  • Manejo automático del ciclo de vida del iterador: Python gestiona automáticamente el ciclo de vida del iterador, evitando la necesidad de que los desarrolladores gestionen manualmente el estado del iterador.

Limitaciones

  • Falta de restricciones de tipo: La flexibilidad del "duck typing" puede llevar a errores si un objeto no cumple con las expectativas de la interfaz esperada, lo que puede dificultar el diagnóstico de problemas en tiempo de ejecución.

¿Qué aprendimos?

En esta lección, exploramos cómo se implementa el Iterator Pattern en Python a través del protocolo nativo de iteradores. A lo largo del proceso, identificamos varias similitudes y diferencias con Kotlin, así como los beneficios y limitaciones de este patrón en Python.

Puntos clave

  • Protocolo de iteración en Python: Aprendimos que cualquier objeto en Python puede implementar los métodos __iter__() y __next__() para volverse iterable, lo que permite flexibilidad en el diseño de colecciones personalizadas y su recorrido.
  • Flexibilidad con duck typing: Python se basa en "duck typing", lo que significa que los objetos no necesitan heredar de una clase específica para ser iterables; simplemente necesitan implementar los métodos correctos. Esto proporciona flexibilidad pero también introduce posibles riesgos de errores en tiempo de ejecución si no se implementa correctamente.
  • Manejo de la iteración: Python utiliza la excepción StopIteration para señalar el final de una colección, mientras que en Kotlin se utiliza el método hasNext() para verificar si hay más elementos. Este enfoque en Python permite que el ciclo for maneje automáticamente el fin de la iteración.
  • Iteradores personalizados: Vimos cómo crear un iterador personalizado para una colección como Library, implementando el protocolo de iteración en Python, lo que facilita la separación de la lógica de iteración de la estructura interna de los datos.

Este enfoque en Python hace que el Iterator Pattern sea una herramienta poderosa y flexible para diseñar colecciones iterables personalizadas, con las ventajas de una sintaxis simple y el manejo automático de la iteración en bucles for. Sin embargo, la falta de restricciones de tipo estrictas puede presentar desafíos cuando se implementan sistemas más complejos.

Bibliografías Recomendadas

  • 📚 "Special Methods for Sequences". (2022). Luciano Ramalho, en Fluent Python: Clear, concise, and effective programming, (pp. 397–430.) O’Reilly.