DMarlon Studio

Lección 7 · 7 de 20

El protocolo de los iteradores · Python

Cómo funciona el for por dentro: iter(), next(), StopIteration y una clase propia con __iter__ y __next__ que pagina resultados en Python.

Python · El protocolo de los iteradores en DMarlon Studio: el código del ejemplo y su salida en la terminal

Un for no sabe nada de listas: le pide a la colección un iterador con iter() y luego llama a next() una y otra vez hasta que salta la excepción `StopIteration`. El programa lo hace a mano con ["ok", "fail"]: dos next() devuelven los dos valores y el tercero lanza StopIteration, que se captura.

Cualquier clase puede participar en este protocolo con dos métodos especiales. __iter__ devuelve el iterador; en Paginator devuelve self, porque el propio objeto lleva la cuenta de por dónde va.

__next__ devuelve el siguiente elemento: corta una página de self.items desde self.start, avanza self.start y la devuelve. Cuando start llega al final, lanza StopIteration y el for termina sin error. La última página tiene solo dos pedidos porque el corte de una lista nunca se sale del final.

Como Paginator cumple el protocolo, funciona con todo lo que recorre: for, enumerate(..., start=1) para numerar las páginas, list() para contarlas, sum(), comprensiones…

Este iterador se gasta, igual que un generador: si recorre el mismo Paginator dos veces, la segunda no da nada. Si quiere poder repetir, haga que __iter__ cree un objeto nuevo cada vez o escriba __iter__ como generador con yield.

Código · main.py

class Paginator:    """Iterates over a list of results, one page at a time."""    def __init__(self, items, page_size):        self.items = items        self.page_size = page_size        self.start = 0    def __iter__(self):        return self                   # this object is its own iterator    def __next__(self):        if self.start >= len(self.items):            raise StopIteration       # tells for that there is no more        page = self.items[self.start:self.start + self.page_size]        self.start += self.page_size        return pageorders = [f"#{n}" for n in range(1001, 1011)]# What a for loop does behind the scenes: iter() and next()it = iter(["ok", "fail"])print(next(it), next(it))try:    next(it)except StopIteration:    print("StopIteration: the list is used up")for number, page in enumerate(Paginator(orders, 4), start=1):    print(f"Page {number}: {page}")# Any function that loops accepts our iteratorprint("Pages:", len(list(Paginator(orders, 3))))

Salida · python3 main.py

ok fail
StopIteration: the list is used up
Page 1: ['#1001', '#1002', '#1003', '#1004']
Page 2: ['#1005', '#1006', '#1007', '#1008']
Page 3: ['#1009', '#1010']
Pages: 4

Para recordar

  • for usa iter() una vez y next() hasta StopIteration.
  • __iter__ devuelve el iterador; __next__ da el siguiente valor.
  • Lanzar StopIteration es la forma de decir que no hay más.
  • Un objeto iterable funciona con for, list(), sum(), enumerate()…

Ejercicio

Añada a Paginator un parámetro limit que corte la iteración después de ese número de páginas, aunque queden elementos.