Módulo 1 · Lección 22 de 59
Recorrer listas
Pasar por todos los elementos con for, obtener también el índice con enumerate, cruzar dos listas con zip y acumular resultados.
Una lista guarda muchos valores, pero su utilidad real aparece cuando la recorres. Sumar, contar, filtrar, buscar el mayor o imprimir un informe son todos el mismo gesto: pasar por cada elemento una vez y hacer algo con él.
El for ya lo conoces del recorrido de cadenas y de range. Sobre listas funciona igual, y además hay dos ayudas que te van a ahorrar mucho código torpe: enumerate y zip.
El recorrido básico#
ciudades = ["Madrid", "Sevilla", "Bilbao"]
for ciudad in ciudades:
print(f"Visitando {ciudad}")Visitando Madrid
Visitando Sevilla
Visitando BilbaoLa variable ciudad toma el valor de cada elemento, uno por vuelta, en orden. No hay que decirle cuántos hay ni manejar ningún contador: el for se para cuando la lista se acaba.
Esta es la manera torpe de hacer lo mismo, y no deberías escribirla:
ciudades = ["Madrid", "Sevilla", "Bilbao"]
for posicion in range(len(ciudades)):
print(f"Visitando {ciudades[posicion]}")Visitando Madrid
Visitando Sevilla
Visitando BilbaoFunciona, pero pasas por un índice que no necesitas y el código dice menos. Resérvalo para cuando el índice te haga falta de verdad, y en ese caso hay algo mejor.
Cuando necesitas la posición: enumerate#
A veces sí quieres el índice: para numerar una lista en pantalla, o para modificar el elemento en su sitio. enumerate te da los dos valores a la vez, la posición y el elemento:
tareas = ["comprar pan", "llamar al banco", "regar plantas"]
for posicion, tarea in enumerate(tareas):
print(f"{posicion}. {tarea}")0. comprar pan
1. llamar al banco
2. regar plantasFíjate en que el for tiene dos variables separadas por una coma. enumerate entrega en cada vuelta un par de valores y Python los reparte, uno en cada variable. Ese reparto se llama desempaquetado y lo explicaremos a fondo en la lección de tuplas.
El parámetro start#
Empezar la numeración en cero está bien para los índices, pero fatal para una lista que va a leer una persona. start cambia el número inicial sin tocar la lista:
tareas = ["comprar pan", "llamar al banco", "regar plantas"]
for numero, tarea in enumerate(tareas, start=1):
print(f"{numero}. {tarea}")1. comprar pan
2. llamar al banco
3. regar plantasEl elemento sigue siendo el mismo; lo único que cambia es el contador que te devuelve. start=1 es el uso más frecuente con diferencia.
Recorrer dos listas a la vez: zip#
Cuando tienes dos listas relacionadas, una con nombres y otra con notas, quieres emparejarlas posición a posición. zip hace exactamente eso, como los dientes de una cremallera:
alumnos = ["Ana", "Luis", "Marta"]
notas = [8.5, 6.0, 9.25]
for alumno, nota in zip(alumnos, notas):
print(f"{alumno:<8} {nota:>5.2f}")Ana 8.50
Luis 6.00
Marta 9.25Y funciona con más de dos:
productos = ["teclado", "ratón", "monitor"]
precios = [29.99, 15.50, 189.00]
unidades = [2, 3, 1]
for producto, precio, cantidad in zip(productos, precios, unidades):
total = precio * cantidad
print(f"{producto:<10} {cantidad} x {precio:>6.2f} = {total:>7.2f} €")teclado 2 x 29.99 = 59.98 €
ratón 3 x 15.50 = 46.50 €
monitor 1 x 189.00 = 189.00 €No modifiques una lista mientras la recorres#
Esto es lo más importante de la lección. Parece razonable recorrer una lista y borrar por el camino lo que no te interesa. No lo es, y el resultado no es un error: es un resultado mal.
numeros = [1, 2, 3, 4, 5, 6]
for numero in numeros:
if numero % 2 == 0:
numeros.remove(numero)
print(numeros)[1, 3, 5]Aquí ha salido bien por casualidad. Cambia los datos y mira:
numeros = [1, 2, 4, 6, 7]
for numero in numeros:
if numero % 2 == 0:
numeros.remove(numero)
print(numeros)[1, 4, 7]El 4 sigue ahí. El motivo es que el for avanza por posiciones y la lista se encoge bajo sus pies. Al borrar el 2, que estaba en la posición 1, el 4 pasa a ocupar esa posición 1; pero el for ya va por la 2, así que se salta el 4 sin mirarlo.
La solución es no tocar la lista que recorres. Tienes dos opciones limpias:
numeros = [1, 2, 4, 6, 7]
impares = []
for numero in numeros:
if numero % 2 != 0:
impares.append(numero)
print(impares)[1, 7]O recorrer una copia y modificar la original, usando el rebanado completo [:] que ya conoces de las cadenas:
numeros = [1, 2, 4, 6, 7]
for numero in numeros[:]: # recorremos una copia
if numero % 2 == 0:
numeros.remove(numero) # modificamos la original
print(numeros)[1, 7]Patrones de acumulación#
Casi todo lo que harás con un for sobre una lista encaja en uno de estos cuatro moldes. Merece la pena tenerlos memorizados.
Sumar#
gastos = [25.50, 13.20, 60.00, 8.75]
total = 0
for gasto in gastos:
total += gasto
print(f"Total: {total:.2f} €")
print(f"Media: {total / len(gastos):.2f} €")Total: 107.45 €
Media: 26.86 €La clave es inicializar el acumulador antes del bucle y a 0. Si lo inicializas dentro, se reinicia en cada vuelta.
Contar los que cumplen algo#
notas = [7.5, 4.0, 9.25, 3.5, 6.0, 8.5]
aprobados = 0
suspensos = 0
for nota in notas:
if nota >= 5:
aprobados += 1
else:
suspensos += 1
print(f"Aprobados: {aprobados}")
print(f"Suspensos: {suspensos}")
print(f"Porcentaje de aprobados: {aprobados / len(notas) * 100:.1f} %")Aprobados: 4
Suspensos: 2
Porcentaje de aprobados: 66.7 %Filtrar en una lista nueva#
temperaturas = [18.2, 24.5, 31.0, 27.8, 15.4, 33.2]
calurosas = []
for temperatura in temperaturas:
if temperatura > 25:
calurosas.append(temperatura)
print(calurosas)
print(f"{len(calurosas)} días de más de 25 grados")[31.0, 27.8, 33.2]
3 días de más de 25 gradosEncontrar el máximo a mano#
Python trae max(), pero conviene saber cómo se hace, porque el molde sirve para cualquier "el mejor de" que no sea un simple número:
temperaturas = [18.2, 24.5, 31.0, 27.8, 15.4]
maxima = temperaturas[0]
for temperatura in temperaturas:
if temperatura > maxima:
maxima = temperatura
print(f"Máxima: {maxima}")Máxima: 31.0Se arranca con el primer elemento, no con cero. Si empezaras con maxima = 0 y todas las temperaturas fueran negativas, el resultado sería un cero que no está en la lista.
Si además quieres saber dónde estaba el máximo, aquí sí hace falta enumerate:
temperaturas = [18.2, 24.5, 31.0, 27.8, 15.4]
maxima = temperaturas[0]
dia_maximo = 0
for dia, temperatura in enumerate(temperaturas, start=1):
if temperatura > maxima:
maxima = temperatura
dia_maximo = dia
print(f"La máxima fue de {maxima} grados, el día {dia_maximo}")La máxima fue de 31.0 grados, el día 3Salir antes de tiempo#
break y continue funcionan igual que en cualquier otro bucle, y con listas son muy útiles para dejar de buscar cuando ya has encontrado:
usuarios = ["ana", "luis", "marta", "pedro"]
buscado = "marta"
posicion_encontrada = -1
for posicion, usuario in enumerate(usuarios):
if usuario == buscado:
posicion_encontrada = posicion
break
if posicion_encontrada >= 0:
print(f"Encontrado en la posición {posicion_encontrada}")
else:
print("No está en la lista")Encontrado en la posición 2El break corta el bucle en cuanto hay resultado, y en una lista grande eso ahorra la mitad del trabajo de media. La variable inicializada a -1 hace de aviso de "todavía no lo he encontrado", que es el mismo convenio que usa el método find de las cadenas.
Recorrer al revés y por partes#
puntuaciones = [10, 25, 40, 55, 70]
for puntuacion in reversed(puntuaciones):
print(puntuacion)
print(puntuaciones[::2])
print(puntuaciones)70
55
40
25
10
[10, 40, 70]
[10, 25, 40, 55, 70]reversed() recorre la lista de atrás hacia delante sin modificarla, y el rebanado con paso te deja saltarte elementos. Ninguno de los dos altera la lista original, como demuestra la última línea.