W3docs

Iteradores de Python

Aprende cómo funcionan los iteradores de Python, cómo crear clases iteradoras personalizadas y cuándo usarlos en lugar de listas.

Un iterador es una de las abstracciones más fundamentales de Python. Cada vez que escribes un bucle for, llamas a zip() o usas una comprensión de lista, Python utiliza silenciosamente el protocolo de iterador por debajo. Este capítulo explica qué son los iteradores, cómo crear los tuyos propios, cómo usar el amplio conjunto de iteradores integrados y cuándo los iteradores son la herramienta adecuada para el trabajo.

¿Qué es un iterador?

Python distingue entre dos conceptos relacionados:

  • Un iterable es cualquier objeto sobre el que puedes iterar — una list, tuple, str, dict, set o cualquier objeto cuya clase defina __iter__. Puede producir un iterador, pero no rastrea la posición por sí mismo.
  • Un iterador es un objeto que rastrea el estado del recorrido. Implementa dos métodos que juntos forman el protocolo de iterador:
    • __iter__() — devuelve el objeto iterador en sí. Esto permite que los iteradores funcionen dentro de bucles for y otros contextos de iteración.
    • __next__() — devuelve el siguiente valor cada vez que se llama. Cuando no quedan valores, lanza StopIteration.

La diferencia clave: puedes recorrer una lista tantas veces como quieras porque cada bucle for solicita un iterador nuevo. Un iterador es unidireccional y de un solo uso — una vez agotado, llamar a next() siempre lanza StopIteration.

python— editable, runs on the server
graph LR
  A[Iterator Object] --> B[__iter__]
  B --> C[Returns self]
  A --> D[__next__]
  D --> E[Next Value]
  D --> F{No values left?}
  F -->|Yes| G[Raises StopIteration]
  F -->|No| E

Cómo usa los iteradores un bucle for

El bucle for no es más que azúcar sintáctico para el protocolo de iterador. Internamente, Python traduce:

for item in some_iterable:
    print(item)

en algo así:

_it = iter(some_iterable)   # call __iter__()
while True:
    try:
        item = next(_it)    # call __next__()
    except StopIteration:
        break
    print(item)

Comprender esta traducción aclara por qué cualquier objeto que implemente __iter__ y __next__ funciona sin problemas en un bucle for, con zip(), enumerate() y cualquier otro contexto que espere un iterable.

Construyendo un iterador personalizado

Para crear un iterador personalizado, define una clase que implemente tanto __iter__ como __next__. A continuación se muestra un iterador Countdown que cuenta hacia atrás desde un número dado hasta 1:

class Countdown:
    def __init__(self, start):
        self.current = start

    def __iter__(self):
        return self        # the iterator is its own iterable

    def __next__(self):
        if self.current <= 0:
            raise StopIteration
        value = self.current
        self.current -= 1
        return value

for n in Countdown(5):
    print(n)
# Output:
# 5
# 4
# 3
# 2
# 1

Observa que __iter__ devuelve self. Esto es lo que permite colocar el mismo objeto directamente en un bucle for — el bucle llama a iter() sobre él, lo que llama a __iter__(), que devuelve el propio iterador.

Añadiendo un parámetro de paso

Puedes añadir cualquier lógica que desees dentro de __next__. A continuación se muestra un iterador StepRange que imita a range() pero acepta un valor de paso:

class StepRange:
    def __init__(self, start, stop, step=1):
        self.current = start
        self.stop = stop
        self.step = step

    def __iter__(self):
        return self

    def __next__(self):
        if self.current >= self.stop:
            raise StopIteration
        value = self.current
        self.current += self.step
        return value

print(list(StepRange(0, 10, 3)))
# Output: [0, 3, 6, 9]

Llamar a list() sobre cualquier iterador lo agota y recopila todos los valores en una lista — un patrón útil cuando necesitas todos los resultados a la vez.

Las funciones integradas iter() y next()

Las funciones integradas iter() y next() son la forma estándar de trabajar directamente con el protocolo de iterador.

  • iter(obj) — llama a obj.__iter__() y devuelve el iterador resultante.
  • next(it) — llama a it.__next__() y devuelve el siguiente valor.
  • next(it, default) — devuelve default en lugar de lanzar StopIteration cuando el iterador está agotado. Es la forma más segura de obtener el siguiente elemento sin un bloque try/except.
words = ["hello", "world"]
it = iter(words)

print(next(it))           # hello
print(next(it))           # world
print(next(it, "done"))   # done  (exhausted; returns default)

La forma de dos argumentos de next() es especialmente útil en escenarios de transmisión o análisis donde deseas manejar el fin de la entrada de forma elegante.

Los iteradores son de un solo uso

Esta es la trampa más común con los iteradores: una vez agotado, un iterador no puede rebobinarse.

it = iter([1, 2, 3])

for x in it:
    print(x)        # prints 1, 2, 3

for x in it:
    print(x)        # prints nothing — iterator is exhausted

Si necesitas iterar varias veces, conserva el iterable original (por ejemplo, la lista) y llama a iter() de nuevo, o usa una comprensión de lista para materializar todos los valores primero.

Funciones integradas que devuelven iteradores

La biblioteca estándar de Python está construida sobre iteradores. Todas estas funciones devuelven iteradores en lugar de listas, por lo que son eficientes en memoria incluso con secuencias muy grandes:

range()

range(start, stop, step) devuelve un iterador de enteros. No almacena los enteros en memoria — los calcula bajo demanda.

for i in range(1, 6):
    print(i)
# Output: 1  2  3  4  5

zip()

zip() toma múltiples iterables y devuelve un iterador de tuplas, emparejando los elementos posición a posición. La iteración se detiene en la entrada más corta.

names = ["Alice", "Bob", "Carol"]
scores = [95, 88, 72]

for name, score in zip(names, scores):
    print(f"{name}: {score}")
# Output:
# Alice: 95
# Bob: 88
# Carol: 72

enumerate()

enumerate() envuelve cualquier iterable y devuelve pares (índice, valor). Úsalo para evitar mantener una variable contadora manual.

fruits = ["apple", "banana", "cherry"]

for i, fruit in enumerate(fruits, start=1):
    print(f"{i}. {fruit}")
# Output:
# 1. apple
# 2. banana
# 3. cherry

map() y filter()

Ambas funciones devuelven iteradores (en Python 3). map(fn, iterable) aplica una función a cada elemento; filter(fn, iterable) conserva solo los elementos para los que la función devuelve True.

numbers = [1, 2, 3, 4, 5]

doubled = list(map(lambda x: x * 2, numbers))
print(doubled)          # [2, 4, 6, 8, 10]

evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens)            # [2, 4]

Verificar si un objeto es un iterador

Usa isinstance() con las clases base abstractas del módulo collections.abc para comprobar si un objeto es iterable o un iterador:

from collections.abc import Iterable, Iterator

my_list = [1, 2, 3]
my_iter = iter(my_list)

print(isinstance(my_list, Iterable))   # True  — list is iterable
print(isinstance(my_list, Iterator))   # False — list is NOT an iterator
print(isinstance(my_iter, Iterator))   # True  — list_iterator is an iterator
print(isinstance(my_iter, Iterable))   # True  — all iterators are also iterables

Todo iterador es también un iterable (porque __iter__ devuelve self), pero no todo iterable es un iterador.

Cuándo usar iteradores frente a listas

SituaciónUsar
Necesitas acceso aleatorio (items[5])list
Necesitas iterar una vez, la memoria importaiterador / generador
Secuencias infinitas o muy grandesiterador / generador
Necesitas iterar varias veceslist (conserva el original)
Cadena de transformacionesiteradores encadenados (map, filter, itertools)

Para conjuntos de datos grandes — leer millones de filas de un archivo, procesar datos en flujo — un iterador evita cargar todo en memoria a la vez. Para colecciones pequeñas y finitas donde accedes a los elementos repetidamente, una lista es más sencilla.

Iteradores frente a generadores

Un generador es una forma abreviada conveniente de escribir un iterador. En lugar de una clase con __iter__ y __next__, escribes una función que usa yield. Python la convierte automáticamente en un iterador.

# Iterator class
class Countdown:
    def __init__(self, start):
        self.current = start
    def __iter__(self):
        return self
    def __next__(self):
        if self.current <= 0:
            raise StopIteration
        value = self.current
        self.current -= 1
        return value

# Equivalent generator function
def countdown(start):
    while start > 0:
        yield start
        start -= 1

print(list(countdown(5)))   # [5, 4, 3, 2, 1]

Usa un iterador basado en clases cuando necesites métodos adicionales o estado mutable más allá de lo que ofrece un generador simple. Usa un generador para la mayoría de los demás casos — es más conciso e igual de potente.

Consulta el capítulo Generadores de Python para un tratamiento completo de yield, expresiones generadoras y send().

Práctica

Práctica
Which methods make up the Python iterator protocol?
Which methods make up the Python iterator protocol?
Was this page helpful?