Qué es un iterable, cómo funciona un iterador, diferencias clave entre ambos, y cómo crear iteradores personalizados.
# Patrón completo: clase iterable + clase iteradorclass IteradorLista: def __init__(self, data): self.data = data.copy() # copia para no modificar el original def __iter__(self): return self # el iterador es iterable: retorna self def __next__(self): if not self.data: raise StopIteration('sin elementos') return self.data.pop(0) class IterableLista: def __init__(self, data): self.data = data def __iter__(self): return IteradorLista(self.data) # siempre crea un iterador NUEVO nums = IterableLista([1, 2, 3])for n in nums: print(n, end=' ') # 1 2 3Si el iterador no implementa __iter__(), el for lanza TypeError al intentar usarlo directamente. El for siempre llama primero a __iter__() en el objeto que recibe, por eso el iterador también debe implementarlo (retornando self).
Cada llamada a iter(iterable) crea un nuevo iterador con su propio estado. Esto permite pausar un recorrido y retomarlo, o tener varios recorridos simultáneos sobre el mismo iterable.
nums = [1, 2, 3]it1 = iter(nums)it2 = iter(nums) # Avanzamos it1 parcialmentefor n in it1: print(n, end=' ') if n >= 2: break # imprime: 1 2 # it2 es independiente: empieza desde el principiofor n in it2: print(n, end=' ') # imprime: 1 2 3 # it1 continua desde donde parofor n in it1: print(n, end=' ') # imprime: 3Usa herencia para reutilizar la lógica de __next__(): define un iterador base con el comportamiento de avance, y crea subclases que solo sobreescriban __iter__() para cambiar el orden (ordenado, inverso, aleatorio, filtrado, etc.).