5.1 Iterable
y Iterator
Como ya sabes, los iteradores son objetos que implementan el protocolo de iterador, permitiendo obtener elementos de una colección uno a uno. Los iteradores se utilizan ampliamente en Python para recorrer los elementos de secuencias como listas, tuplas y cadenas de texto.
Veamos cómo están estructurados los iteradores y cómo utilizarlos.
Objeto iterativo (Iterable)
Para que un objeto pueda recorrerse con un bucle for
, debe
ser iterable – Iterable
. Esto significa que nuestro objeto debe
implementar el método __iter__()
,
que devuelve un objeto iterador.
Objeto iterador (Iterator)
Este es un objeto especial que tiene
la función __next__()
para devolver
el siguiente elemento de la secuencia. Cuando los elementos
se acaban, el método __next__()
lanza una excepción
StopIteration
como señal de que la iteración ha finalizado.
El iterador también debe implementar el método __iter__()
,
que devuelve el propio iterador.
Ejemplo usando funciones integradas de Python
En este ejemplo, la lista
numbers es un objeto iterable.
Obtenemos un iterador usando la función
iter()
y usamos la función
next()
para recorrer los elementos hasta que
se lance la excepción
StopIteration
.
# Objeto iterable
numbers = [1, 2, 3, 4, 5]
# Obtenemos un iterador del objeto iterable
iterator = iter(numbers)
# Usamos el iterador para recorrer los elementos
try:
while True:
number = next(iterator)
print(number)
except StopIteration:
pass
Es precisamente lo que sucede cuando escribes un código del tipo:
# Objeto iterable
numbers = [1, 2, 3, 4, 5]
for number in numbers:
print(number)
5.2 La esencia del iterador
Un iterador es un objeto que nos ayuda a recorrer un grupo de elementos uno a uno. Sus implementaciones pueden ser diversas. Vamos a escribir nuestra propia clase, en la que implementamos todos los requisitos que se exigen a un iterador.
Paso 1. Para comenzar, crearemos nuestra propia clase
Que devuelva secuencialmente números de start
a
end
class MyIterator:
def __init__(self, start, end):
self.current = start
self.end = end
Paso 2. Soporte para la función __iter__
Ahora necesitamos añadirle la función __iter__
,
que devolverá un objeto iterador, en el que se llamará
la función __next()__
. Vamos a devolver
una referencia a nuestro propio objeto – esto no está prohibido.
class MyIterator:
def __init__(self, start, end):
self.current = start
self.end = end
def __iter__(self):
return self
Paso 3. Soporte para la función __next__
Ahora necesitamos añadir a nuestro objeto iterador la función __next__
, que devolverá
el siguiente elemento de nuestra lista. Simplemente usaremos la variable current
:
def __next__(self):
current = self.current
self.current += 1
return current
Paso 4. Detener el iterador
Si el iterador ya ha devuelto todos los valores que planeaba, debe lanzar una excepción StopIteration
. Vamos a
ajustar un poco nuestra última función:
def __next__(self):
if self.current >= self.end:
raise StopIteration
current = self.current
self.current += 1
return current
Genial. Ahora podemos usar nuestro iterador. Aquí tienes un ejemplo de todo nuestro código:
class MyIterator:
def __init__(self, start, end):
self.current = start
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.current >= self.end:
raise StopIteration
current = self.current
self.current += 1
return current
# Creamos una instancia del iterador personalizado
my_iter = MyIterator(1, 5)
# Usamos el iterador para recorrer los elementos
for num in my_iter:
print(num)
5.3 Un iterador correcto
¿Qué tiene de malo el iterador del ejemplo anterior? Sí, es un iterador, funciona, pero es demasiado primitivo. Con él no es posible recorrer la misma colección de elementos simultáneamente con diferentes iteradores.
Sería más adecuado escribir un código que no devolviera una referencia a sí mismo en el método __iter__
, sino un
objeto separado,
que ya devuelva correctamente todos los elementos.
Ejemplo:
class MyIterable:
def __init__(self, data):
self.data = data
def __iter__(self):
return MyIterator(self.data)
class MyIterator:
def __init__(self, data):
self.data = data
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index >= len(self.data):
raise StopIteration
item = self.data[self.index]
self.index += 1
return item
# Uso
my_iterable = MyIterable([1, 2, 3, 4])
for item in my_iterable:
print(item)
En este ejemplo tenemos dos clases — a la primera se le pasa la colección, por la que vamos a recorrer con un iterador. Y
la segunda es el iterador en sí, que devuelve los elementos de la colección en el método next()
. También es bastante simple,
pero así es como debes añadir iteradores a tus clases.
GO TO FULL VERSION