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