En la unidad anterior se hizo referencia a que todas las secuencias pueden ser recorridas mediante una misma estructura (for variable in secuencia), ya que todas implementan el método especial __iter__. Este método debe devolver un iterador capaz de recorrer la secuencia como corresponda.

Nota Un iterador es un objeto que permite recorrer uno a uno los elementos almacenados en una estructura de datos, y operar con ellos.

En particular, en Python, los iteradores tienen que implementar un método next que debe devolver los elementos, de a uno por vez, comenzando por el primero. Y al llegar al final de la estructura, debe levantar una excepción de tipo StopIteration.

Es decir que las siguientes estructuras son equivalentes

for elemento in secuencia:
    # hacer algo con elemento

iterador = iter(secuencia)
while True:
    try:
        elemento = iterador.next()
    except StopIteration:
        break
    # hacer algo con elemento

En particular, si queremos implementar un iterador para la lista enlazada, la mejor solución implica crear una nueva clase, _IteradorListaEnlazada, que implemente el método next() de la forma apropiada.

Advertencia Utilizamos la notación de clase privada, utilizada también para la clase _Nodo, ya que si bien se devolverá el iterador cuando sea necesario, un programador externo no debería construir el iterador sin pasar a través de la lista enlazada.

Para inicializar la clase, lo único que se necesita es una referencia al primer elemento de la lista.

class _IteradorListaEnlazada(object):
    " Iterador para la clase ListaEnlazada "
    def __init__(self, prim):
        """ Constructor del iterador.
            prim es el primer elemento de la lista. """
        self.actual = prim

A partir de allí, el iterador irá avanzando a través de los elementos de la lista mediante el método next. Para verificar que no se haya llegado al final de la lista, se corroborará que la referencia self.actual sea distinta de None.

if self.actual == None:
    raise StopIteration("No hay más elementos en la lista")

Una vez que se pasó la verificación, la primera llamada a next debe devolver el primer elemento, pero también debe avanzar, para que la siguiente llamada devuelva el siguiente elemento. Por ello, se utiliza la estructura guardar, avanzar, devolver.

# Guarda el dato
dato = self.actual.dato
# Avanza en la lista
self.actual = self.actual.prox
# Devuelve el dato
return dato

En el Código 16.4 se puede ver el código completo del iterador.

# Código 16.4: _IteradorListaEnlazada: Un iterador para la lista enlazada

class _IteradorListaEnlazada(object):
    " Iterador para la clase ListaEnlazada "
    def __init__(self, prim):
        """ Constructor del iterador.
            prim es el primer elemento de la lista. """
        self.actual = prim

    def next(self):
        """ Devuelve uno a uno los elementos de la lista. """
        if self.actual == None:
            raise StopIteration("No hay más elementos en la lista")

        # Guarda el dato
        dato = self.actual.dato
        # Avanza en la lista
        self.actual = self.actual.prox
        # Devuelve el dato
        return dato

Finalmente, una vez que se tiene el iterador implementado, es necesario modificar la clase ListaEnlazada para que devuelva el iterador cuando se llama al método __iter__.

def __iter__(self):
    " Devuelve el iterador de la lista. "
    return _IteradorListaEnlazada(self.prim)

Con todo esto será posible recorrer nuestra lista con la estructura a la que estamos acostumbrados.

>>> l = ListaEnlazada()
>>> l.append(1)
>>> l.append(3)
>>> l.append(5)
>>> for valor in l:
...     print valor
...
1
3
5

Copyright (c) 2011-2014 Rosita Wachenchauzer, Margarita Manterola, Maximiliano Curia, Marcos Medrano, Nicolás Paez. La copia y redistribución de esta página se permite bajo los términos de la licencia Creative Commons Atribución - Compartir Obras Derivadas Igual 3.0 siempre que se conserve esta nota de copyright.