CertiDevs: Iteradores y protocolos de iteración

Test de programación
Curso de CertiDevs
Actualizado: 05/05/2026

Ejercicio de programación: Iteradores y protocolos de iteración

Este ejercicio de programación está diseñado para poner a prueba tus conocimientos en CertiDevs.

Tipo: Test de opción múltiple

Contenido del ejercicio

Concepto básico de iterables e iteradores

En Python, la iteración es una de las operaciones más fundamentales y poderosas. Cuando recorremos una lista con un bucle for, consultamos elementos de un diccionario, o aplicamos una comprensión de lista, estamos aprovechando el sistema de iteración de Python.

Detrás de estas operaciones cotidianas se esconden dos conceptos clave: los iterables y los iteradores.

Iterables

Un iterable es cualquier objeto en Python que puede ser recorrido elemento por elemento. Dicho de manera más técnica, un iterable es un objeto que implementa el método __iter__(), el cual devuelve un iterador.

Los tipos de datos más comunes en Python son iterables:

  • Listas: [1, 2, 3]
  • Tuplas: (1, 2, 3)
  • Diccionarios: {'a': 1, 'b': 2}
  • Conjuntos: {1, 2, 3}
  • Cadenas: "Python"
  • Archivos: objetos devueltos por open()

Podemos comprobar si un objeto es iterable utilizando la función isinstance() junto con collections.abc.Iterable:

from collections.abc import Iterable

# Comprobamos si varios objetos son iterables
print(isinstance([1, 2, 3], Iterable))  # True
print(isinstance("Python", Iterable))   # True
print(isinstance(123, Iterable))        # False

Iteradores

Un iterador es un objeto que representa un flujo de datos. A diferencia de un iterable, que simplemente define la capacidad de ser recorrido, un iterador es el mecanismo que realiza el recorrido real, manteniendo el estado de la iteración y sabiendo cuál es el siguiente elemento.

Un iterador debe implementar dos métodos:

  • __iter__(): Devuelve el propio iterador (esto hace que los iteradores también sean iterables).
  • __next__(): Devuelve el siguiente elemento en la secuencia. Cuando no hay más elementos, lanza la excepción StopIteration.

Podemos visualizar un iterador como un marcador de libro que recuerda dónde nos quedamos en la lectura. Cada vez que llamamos a next(), el marcador avanza a la siguiente página.

Obteniendo un iterador de un iterable

Para obtener un iterador a partir de un iterable, utilizamos la función iter():

# Creamos una lista (iterable)
numeros = [10, 20, 30]

# Obtenemos un iterador de la lista
iterador = iter(numeros)

# Usamos next() para obtener elementos uno por uno
print(next(iterador))  # 10
print(next(iterador))  # 20
print(next(iterador))  # 30

# Si intentamos obtener más elementos, se lanza StopIteration
try:
    print(next(iterador))
except StopIteration:
    print("No hay más elementos")

Iteradores de un solo uso

Una característica importante de los iteradores es que son objetos de un solo uso. Una vez que un iterador ha recorrido todos los elementos, queda "agotado" y no puede reiniciarse automáticamente:

letras = ['a', 'b', 'c']
iter_letras = iter(letras)

# Consumimos todos los elementos
for letra in iter_letras:
    print(letra)  # Imprime a, b, c

# El iterador ya está agotado
for letra in iter_letras:
    print(letra)  # No imprime nada

Para volver a recorrer la secuencia, necesitamos crear un nuevo iterador:

# Creamos un nuevo iterador
iter_letras_nuevo = iter(letras)
for letra in iter_letras_nuevo:
    print(letra)  # Imprime a, b, c de nuevo

Iterables infinitos

Una ventaja de los iteradores es que pueden representar secuencias infinitas sin necesidad de almacenar todos los elementos en memoria. Por ejemplo, podemos crear un iterador que genere números enteros indefinidamente:

class ContadorInfinito:
    def __init__(self, inicio=0):
        self.numero = inicio
        
    def __iter__(self):
        return self
        
    def __next__(self):
        valor = self.numero
        self.numero += 1
        return valor

# Creamos un contador infinito
contador = ContadorInfinito(1)

# Obtenemos algunos valores
print(next(contador))  # 1
print(next(contador))  # 2
print(next(contador))  # 3

# Podemos usar un bucle for con límite para evitar un bucle infinito
for i in contador:
    print(i)
    if i >= 10:
        break  # Detenemos en 10

Iterables vs. Iteradores: diferencias clave

Es importante entender la distinción entre iterables e iteradores:

  • Un iterable es un objeto que puede proporcionar un iterador (mediante __iter__())
  • Un iterador es el objeto que mantiene el estado durante la iteración (implementa __next__())

Esta separación permite que un mismo iterable pueda ser recorrido múltiples veces, cada vez con un iterador independiente:

palabras = ["Python", "es", "genial"]

# Creamos dos iteradores independientes
iter1 = iter(palabras)
iter2 = iter(palabras)

# Avanzamos el primer iterador
print(next(iter1))  # "Python"
print(next(iter1))  # "es"

# El segundo iterador mantiene su propio estado
print(next(iter2))  # "Python" (comienza desde el principio)

Iterables personalizados

Podemos crear nuestros propios objetos iterables implementando el método __iter__(). Veamos un ejemplo de una clase que genera los primeros n números de la secuencia de Fibonacci:

class Fibonacci:
    def __init__(self, limite):
        self.limite = limite
        
    def __iter__(self):
        self.a, self.b = 0, 1
        self.contador = 0
        return self
        
    def __next__(self):
        if self.contador < self.limite:
            resultado = self.a
            self.a, self.b = self.b, self.a + self.b
            self.contador += 1
            return resultado
        else:
            raise StopIteration

# Creamos un iterable de Fibonacci con límite 8
fib = Fibonacci(8)

# Recorremos los números
for numero in fib:
    print(numero)  # Imprime: 0, 1, 1, 2, 3, 5, 8, 13

Iterables y bucles for

Cuando usamos un bucle for en Python, estamos aprovechando el protocolo de iteración de forma implícita. El bucle for hace lo siguiente:

  1. Llama a iter() sobre el objeto iterable para obtener un iterador
  2. Llama repetidamente a next() sobre ese iterador
  3. Captura la excepción StopIteration para saber cuándo detenerse

Este es el equivalente manual de un bucle for:

colores = ["rojo", "verde", "azul"]

# Forma explícita (equivalente a un bucle for)
iterador = iter(colores)
try:
    while True:
        color = next(iterador)
        print(color)
except StopIteration:
    pass

# La forma habitual con for (hace lo mismo internamente)
for color in colores:
    print(color)

Ventajas de los iteradores

Los iteradores ofrecen varias ventajas importantes:

  • Eficiencia de memoria: No necesitan cargar todos los elementos en memoria a la vez.
  • Evaluación perezosa: Los elementos se generan solo cuando se necesitan.
  • Composición: Los iteradores pueden encadenarse y combinarse para crear flujos de datos complejos.
  • Abstracción: Proporcionan una interfaz uniforme para recorrer diferentes tipos de colecciones.

Estas características hacen que los iteradores sean especialmente útiles cuando trabajamos con conjuntos de datos grandes o potencialmente infinitos.

Protocolo de iteración: iter() y next()

El protocolo de iteración en Python es el mecanismo formal que permite a los objetos comportarse como iterables e iteradores. Este protocolo define cómo los objetos pueden ser recorridos secuencialmente y es la base de muchas operaciones en Python, como los bucles for, las comprensiones de listas, y funciones como map() o filter().

El protocolo de iteración se implementa mediante dos funciones integradas clave: iter() y next().

La función iter()

La función iter() es la puerta de entrada al protocolo de iteración. Cuando llamamos a iter() sobre un objeto, Python busca implementar uno de estos dos comportamientos:

  • Llama al método __iter__() del objeto, que debe devolver un iterador.
  • Si el objeto no tiene __iter__() pero implementa __getitem__(), Python crea un iterador que accede a los elementos por índice, comenzando desde 0.
# Obteniendo un iterador de diferentes tipos de datos
lista_iter = iter([1, 2, 3])
cadena_iter = iter("Python")
dict_iter = iter({"a": 1, "b": 2})  # Itera sobre las claves

# También funciona con objetos que implementan __getitem__
class MiSecuencia:
    def __getitem__(self, indice):
        if indice < 5:
            return indice * 2
        raise IndexError()

seq = MiSecuencia()
seq_iter = iter(seq)  # Funciona aunque no tenga __iter__()

La función next()

Una vez que tenemos un iterador, usamos la función next() para obtener el siguiente elemento en la secuencia. Internamente, next() llama al método __next__() del iterador:

numeros = [10, 20, 30]
iterador = iter(numeros)

# Obtenemos elementos uno por uno
primer_elemento = next(iterador)  # 10
segundo_elemento = next(iterador)  # 20
tercer_elemento = next(iterador)   # 30

Cuando no quedan más elementos, next() lanza la excepción StopIteration:

try:
    cuarto_elemento = next(iterador)  # No existe
except StopIteration:
    print("El iterador se ha agotado")

Valor por defecto en next()

La función next() acepta un segundo argumento opcional que específica un valor por defecto a devolver cuando el iterador se agota, en lugar de lanzar StopIteration:

letras = ['a', 'b']
iter_letras = iter(letras)

print(next(iter_letras))                # 'a'
print(next(iter_letras))                # 'b'
print(next(iter_letras, "fin"))         # 'fin' (valor por defecto)
print(next(iter_letras, "fin"))         # 'fin' (valor por defecto)

Este patrón es útil cuando queremos manejar el final de la iteración de forma elegante sin usar bloques try-except.

Implementando el protocolo en clases personalizadas

Para crear objetos que funcionen con el protocolo de iteración, necesitamos implementar los métodos adecuados:

class ContadorPares:
    """Iterador que genera números pares hasta un límite"""
    
    def __init__(self, limite):
        self.limite = limite
        self.valor = 0
    
    def __iter__(self):
        # Un iterador debe devolver self en __iter__
        return self
    
    def __next__(self):
        if self.valor >= self.limite:
            raise StopIteration
        
        resultado = self.valor
        self.valor += 2
        return resultado

# Uso del iterador personalizado
pares = ContadorPares(10)
for numero in pares:
    print(numero)  # Imprime: 0, 2, 4, 6, 8

Separando iterable e iterador

Una práctica recomendada es separar la responsabilidad entre el iterable (que proporciona iteradores) y el iterador (que realiza el recorrido):

class RangoInverso:
    """Iterable que genera números en orden inverso"""
    
    def __init__(self, inicio, fin):
        self.inicio = inicio
        self.fin = fin
    
    def __iter__(self):
        # Devuelve un nuevo iterador cada vez
        return RangoInversoIterador(self.inicio, self.fin)

class RangoInversoIterador:
    """Iterador para RangoInverso"""
    
    def __init__(self, inicio, fin):
        self.actual = inicio
        self.fin = fin
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.actual < self.fin:
            raise StopIteration
        
        resultado = self.actual
        self.actual -= 1
        return resultado

# Uso del iterable con iteradores independientes
numeros = RangoInverso(10, 5)

# Podemos crear múltiples iteradores independientes
for n in numeros:
    print(n)  # Imprime: 10, 9, 8, 7, 6

# El iterable se puede recorrer múltiples veces
print("Segunda iteración:")
for n in numeros:
    print(n)  # Imprime: 10, 9, 8, 7, 6 nuevamente

Esta separación permite que un mismo iterable pueda ser recorrido múltiples veces, cada vez con un iterador fresco e independiente.

El protocolo de iteración en acción

Veamos cómo Python utiliza el protocolo de iteración en diferentes contextos:

# 1. Bucle for
colores = ["rojo", "verde", "azul"]
for color in colores:  # Implícitamente llama a iter() y next()
    print(color)

# 2. Comprensión de lista
cuadrados = [x**2 for x in range(5)]  # Usa iteración

# 3. Funciones que consumen iterables
suma = sum(range(10))  # sum() itera sobre range(10)
maximo = max([5, 8, 2, 10])  # max() itera sobre la lista

# 4. Desempaquetado
primero, segundo, *resto = [1, 2, 3, 4, 5]  # Usa iteración

Iteración perezosa y eficiencia

Una de las ventajas clave del protocolo de iteración es la evaluación perezosa (lazy evaluation). Los elementos se generan solo cuando se solicitan, lo que permite trabajar con secuencias potencialmente infinitas o muy grandes:

# Generador que produce números al cuadrado bajo demanda
def cuadrados_infinitos():
    n = 0
    while True:
        yield n ** 2
        n += 1

# Creamos un iterador de cuadrados
cuadrados = cuadrados_infinitos()

# Obtenemos solo los primeros 5 valores
for _ in range(5):
    print(next(cuadrados))  # Imprime: 0, 1, 4, 9, 16

# El iterador podría continuar indefinidamente

Esta característica es especialmente útil cuando procesamos archivos grandes o flujos de datos:

# Procesamiento eficiente de un archivo grande
def procesar_lineas(nombre_archivo):
    with open(nombre_archivo, 'r') as archivo:
        for linea in archivo:  # El archivo es un iterable
            # Procesa cada línea individualmente
            linea = linea.strip()
            if linea:
                yield linea.upper()

# Uso: procesa el archivo línea por línea sin cargarlo completo en memoria
for linea_procesada in procesar_lineas("datos.txt"):
    print(linea_procesada)

Herramientas para trabajar con iteradores

Python proporciona varias herramientas en el módulo itertools para manipular y combinar iteradores:

import itertools

# Limitar un iterador potencialmente infinito
for n in itertools.islice(range(1000000), 5):
    print(n)  # Solo imprime los primeros 5 números

# Combinar iteradores
nombres = ["Ana", "Carlos", "Elena"]
edades = [25, 30, 28]
for nombre, edad in zip(nombres, edades):
    print(f"{nombre} tiene {edad} años")

# Generar combinaciones
for combo in itertools.combinations("ABC", 2):
    print("".join(combo))  # Imprime: AB, AC, BC

Detección de iterables e iteradores

Podemos verificar si un objeto cumple con el protocolo de iteración:

from collections.abc import Iterable, Iterator

# Verificar si un objeto es iterable
print(isinstance([1, 2, 3], Iterable))  # True
print(isinstance(42, Iterable))         # False

# Verificar si un objeto es un iterador
lista = [1, 2, 3]
lista_iter = iter(lista)
print(isinstance(lista, Iterator))      # False
print(isinstance(lista_iter, Iterator)) # True

Esta distinción es importante para entender el comportamiento de diferentes objetos en el contexto de la iteración.

Casos de uso prácticos

El protocolo de iteración facilita patrones de diseño elegantes para muchos problemas comunes:

# Implementación de un rango con paso personalizado
class RangoPaso:
    def __init__(self, inicio, fin, paso=1):
        self.inicio = inicio
        self.fin = fin
        self.paso = paso
    
    def __iter__(self):
        valor = self.inicio
        while valor < self.fin if self.paso > 0 else valor > self.fin:
            yield valor
            valor += self.paso

# Uso del rango personalizado
for i in RangoPaso(1, 10, 2):
    print(i)  # Imprime: 1, 3, 5, 7, 9

# Iteración sobre una estructura de árbol
class Nodo:
    def __init__(self, valor, izquierda=None, derecha=None):
        self.valor = valor
        self.izquierda = izquierda
        self.derecha = derecha
    
    def __iter__(self):
        # Recorrido en orden (inorder traversal)
        if self.izquierda:
            yield from self.izquierda
        yield self.valor
        if self.derecha:
            yield from self.derecha

# Creación de un árbol binario simple
arbol = Nodo(4, 
            Nodo(2, Nodo(1), Nodo(3)), 
            Nodo(6, Nodo(5), Nodo(7)))

# Recorrido del árbol usando el protocolo de iteración
for valor in arbol:
    print(valor)  # Imprime: 1, 2, 3, 4, 5, 6, 7

El protocolo de iteración es uno de los mecanismos más elegantes y potentes de Python, permitiendo escribir código conciso y eficiente para trabajar con secuencias de datos de cualquier tipo.

Más ejercicios de CertiDevs

Explora más ejercicios de programación en CertiDevs para mejorar tus habilidades y obtener tu certificación.

Ver más ejercicios de CertiDevs
Alan Sastre - Autor del ejercicio

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, CertiDevs es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear ejercicios prácticos y contenido educativo de calidad para desarrolladores de todos los niveles.

Solución al ejercicio de programación en CertiDevs

Contenido bloqueado

¡Desbloquea la solución completa!

Completa el ejercicio de programación en CertiDevs para acceder a la solución paso a paso, explicaciones detalladas y mejores prácticas.

solution.js
JavaScript
1 function solveChallenge ( input ) {
2 // Algoritmo optimizado O(n log n)
3 const data = parseInput ( input );
4 const sorted = data . sort (( a , b ) => a - b );
5
6 // Aplicar técnica de dos punteros
7 let left = 0 , right = sorted . length - 1 ;
8 const result = [];
9
10 while ( left < right ) {
11 const sum = sorted [ left ] + sorted [ right ];
12 if ( sum === target ) {
13 result . push ([ sorted [ left ], sorted [ right ]]);
14 left ++; right --;
15 } else if ( sum < target ) {
16 left ++;
17 } else {
18 right --;
19 }
20 }
21
22 return result ;
23 }
Código completo
Explicaciones
Mejores prácticas
+1.200 developers han resuelto este ejercicio de programación

Practica con ejercicios de programación en CertiDevs

Mejora tus habilidades con cientos de ejercicios de práctica, recibe retroalimentación instantánea y obtén tu certificación cuando estés listo.

Asistente de IA

Aprende de tus errores

Progreso

Mide tu avance

Certificación

Valida tus habilidades

Ejercicios de programación en CertiDevs: Práctica y Certificación

Los ejercicios de programación son fundamentales para dominar CertiDevs. Este ejercicio está diseñado para poner a prueba tus conocimientos prácticos y ayudarte a consolidar lo aprendido en las lecciones teóricas. La práctica constante con ejercicios de programación es la clave para convertirte en un desarrollador experto.

¿Por qué resolver ejercicios de programación?

Resolver ejercicios de programación en CertiDevs te permite:

  • Aplicar conocimientos teóricos: Poner en práctica los conceptos aprendidos en las lecciones de CertiDevs.
  • Identificar áreas de mejora: Descubrir qué conceptos necesitas reforzar en tu aprendizaje de CertiDevs.
  • Prepararte para certificaciones: Los ejercicios te preparan para obtener certificados profesionales en CertiDevs.
  • Mejorar tu perfil profesional: Demostrar tus habilidades prácticas en CertiDevs.

Metodología de aprendizaje

Nuestros ejercicios de programación están diseñados siguiendo una metodología probada de aprendizaje progresivo. Cada ejercicio en CertiDevs está cuidadosamente estructurado para llevar tus habilidades al siguiente nivel. Comenzamos con conceptos fundamentales y avanzamos gradualmente hacia desafíos más complejos que reflejan situaciones reales del desarrollo de software profesional.

Certificación y validación de conocimientos

Al completar ejercicios de programación, no solo mejoras tus habilidades técnicas, sino que también puedes obtener certificados que validan tu expertise en CertiDevs. Estos certificados son reconocidos por empresas y pueden ser una gran adición a tu perfil profesional de LinkedIn o tu CV como desarrollador.

Los ejercicios están alineados con los estándares de la industria y cubren desde conceptos básicos hasta técnicas avanzadas de programación en CertiDevs. Cada ejercicio incluye casos de prueba y ejemplos prácticos que te ayudarán a comprender mejor cómo aplicar lo aprendido en proyectos reales.

Nota: Para obtener el máximo beneficio de este ejercicio de programación, te recomendamos revisar primero las lecciones relacionadas de CertiDevs y asegurarte de comprender los conceptos básicos antes de intentar resolver el ejercicio.