Python enumerate, zip, map y filter
Aprende cómo funcionan enumerate, zip, map y filter en Python, con ejemplos claros y patrones prácticos para una iteración más limpia.
Python incluye cuatro funciones integradas — enumerate, zip, map y filter — que reemplazan patrones de bucle comunes con una expresión única y legible. Cada una devuelve un iterador (perezoso y eficiente en memoria), y se combinan de forma natural con los bucles for y las comprensiones de lista.
Este capítulo cubre:
enumerate()— recorre con un índice automáticozip()— itera sobre dos o más secuencias en paralelomap()— aplica una función a cada elementofilter()— conserva solo los elementos que pasan una prueba- Combinación de las cuatro en patrones del mundo real
- Cuándo preferir una comprensión de lista en su lugar
enumerate() — índice durante la iteración
Cuando necesitas tanto la posición como el valor durante un bucle, el enfoque ingenuo utiliza un contador manual:
colors = ["red", "green", "blue"]
i = 0
for color in colors:
print(i, color)
i += 1enumerate() hace esto automáticamente. Envuelve cualquier iterable y produce pares (índice, valor):
colors = ["red", "green", "blue"]
for i, color in enumerate(colors):
print(i, color)Salida:
0 red
1 green
2 blueComenzar desde un índice diferente
Pasa un argumento start para empezar a contar desde cualquier entero:
fruits = ["apple", "banana", "cherry"]
for i, fruit in enumerate(fruits, start=1):
print(f"{i}. {fruit}")Salida:
1. apple
2. banana
3. cherryEsto es útil al construir listas numeradas para mostrar, donde la numeración desde 0 resulta extraña para el lector.
enumerate() con una condición
Como enumerate() es simplemente un iterador normal, puedes agregar pruebas if dentro del cuerpo del bucle:
scores = [72, 91, 55, 88, 44]
for rank, score in enumerate(scores, start=1):
if score >= 80:
print(f"Rank {rank}: score {score} — PASS")Salida:
Rank 2: score 91 — PASS
Rank 4: score 88 — PASSObtener solo el índice sin enumerate()
Si solo necesitas un índice y nada más, range(len(seq)) funciona — pero tan pronto como también necesites el valor, usa enumerate() para evitar escribir seq[i] repetidamente.
zip() — iterar sobre varias secuencias en paralelo
zip() combina dos o más iterables en un único iterador de tuplas, emparejando elementos en posiciones coincidentes:
names = ["Alice", "Bob", "Charlie"]
scores = [95, 87, 92]
for name, score in zip(names, scores):
print(f"{name}: {score}")Salida:
Alice: 95
Bob: 87
Charlie: 92zip() con tres o más iterables
first = ["a", "b", "c"]
second = [1, 2, 3]
third = [True, False, True]
for x, y, z in zip(first, second, third):
print(x, y, z)Salida:
a 1 True
b 2 False
c 3 TrueSecuencias de longitud desigual
zip() se detiene en el iterable más corto y descarta silenciosamente los elementos restantes de los más largos:
short = [1, 2]
long = [10, 20, 30, 40]
print(list(zip(short, long)))Salida:
[(1, 10), (2, 20)]Los valores 30 y 40 de long se descartan. Si necesitas conservar todos los elementos, usa itertools.zip_longest de la biblioteca estándar:
from itertools import zip_longest
short = [1, 2]
long = [10, 20, 30, 40]
print(list(zip_longest(short, long, fillvalue=0)))Salida:
[(1, 10), (2, 20), (0, 30), (0, 40)]Convertir un resultado zip en una lista o diccionario
zip() devuelve un iterador perezoso. Envuélvelo en list() para materializar todos los pares de una vez, o pásalo a dict() para construir un mapa a partir de dos secuencias paralelas:
keys = ["name", "age", "city"]
values = ["Alice", 30, "Paris"]
record = dict(zip(keys, values))
print(record)Salida:
{'name': 'Alice', 'age': 30, 'city': 'Paris'}Descomprimir con zip(*...)
El truco de desempaquetado con * "invierte" una secuencia de tuplas de vuelta a secuencias separadas:
pairs = [(1, "a"), (2, "b"), (3, "c")]
numbers, letters = zip(*pairs)
print(numbers) # (1, 2, 3)
print(letters) # ('a', 'b', 'c')map() — aplicar una función a cada elemento
map(function, iterable) aplica function a cada elemento y devuelve un iterador perezoso de resultados. Reemplaza un bucle for que construye una nueva lista transformando cada elemento.
numbers = [1, 2, 3, 4, 5]
squares = map(lambda x: x ** 2, numbers)
print(list(squares))Salida:
[1, 4, 9, 16, 25]map() con una función nombrada
Puedes pasar cualquier callable — una función integrada, una función definida con def, o una lambda:
words = [" hello ", " world ", " python "]
stripped = list(map(str.strip, words))
print(stripped)Salida:
['hello', 'world', 'python']Aquí str.strip se pasa como referencia (sin paréntesis). map() llama a str.strip(word) para cada elemento.
map() sobre múltiples iterables
Pasa iterables adicionales para mapear una función que toma dos o más argumentos. map() entonces recorre todos los iterables en paralelo (como zip(), se detiene en el más corto):
a = [1, 2, 3]
b = [10, 20, 30]
totals = list(map(lambda x, y: x + y, a, b))
print(totals)Salida:
[11, 22, 33]map() vs comprensión de lista
Ambos enfoques son válidos. La regla general:
| Situación | Preferir |
|---|---|
| Aplicar una función nombrada existente | map() — más corto, sin necesidad de lambda |
| Escribir una expresión en línea | Comprensión de lista — más legible |
| Múltiples entradas en paralelo | map() con múltiples iterables |
Formas equivalentes:
# map() with a named function
result = list(map(str.upper, ["a", "b", "c"]))
# list comprehension — same result
result = [s.upper() for s in ["a", "b", "c"]]filter() — conservar solo los elementos que cumplen una condición
filter(function, iterable) conserva los elementos para los cuales function devuelve un valor verdadero. Devuelve un iterador perezoso.
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = filter(lambda x: x % 2 == 0, numbers)
print(list(evens))Salida:
[2, 4, 6, 8, 10]filter() con una función nombrada
def is_positive(n):
return n > 0
values = [-3, -1, 0, 4, 7, -2]
positives = list(filter(is_positive, values))
print(positives)Salida:
[4, 7]filter(None, iterable) — eliminar valores falsos
Pasar None como función conserva solo los elementos verdaderos. Es una forma rápida de eliminar cadenas vacías, ceros y valores None:
mixed = [0, "hello", "", None, 42, False, "world"]
truthy = list(filter(None, mixed))
print(truthy)Salida:
['hello', 42, 'world']filter() vs comprensión de lista
filter() y una comprensión de lista condicional hacen el mismo trabajo:
numbers = range(1, 11)
# filter()
evens_f = list(filter(lambda x: x % 2 == 0, numbers))
# list comprehension — equivalent
evens_c = [x for x in numbers if x % 2 == 0]
print(evens_f) # [2, 4, 6, 8, 10]
print(evens_c) # [2, 4, 6, 8, 10]Prefiere una comprensión de lista cuando la condición está escrita en línea; prefiere filter() cuando ya tienes una función predicado bien nombrada.
Combinando enumerate, zip, map y filter
Estas cuatro funciones se combinan de forma natural. Aquí hay un ejemplo realista: tienes dos listas paralelas de nombres de estudiantes y puntajes brutos; quieres imprimir un informe numerado que incluya solo los estudiantes que aprobaron (puntaje >= 60), con los puntajes convertidos a calificaciones con letras.
def letter_grade(score):
if score >= 90:
return "A"
elif score >= 80:
return "B"
elif score >= 70:
return "C"
elif score >= 60:
return "D"
return "F"
names = ["Alice", "Bob", "Carol", "Dave", "Eve"]
scores = [85, 52, 91, 67, 48]
# Step 1: pair names with scores
pairs = zip(names, scores)
# Step 2: keep only passing students
passing = filter(lambda pair: pair[1] >= 60, pairs)
# Step 3: convert scores to letter grades
graded = map(lambda pair: (pair[0], letter_grade(pair[1])), passing)
# Step 4: number the results
for rank, (name, grade) in enumerate(graded, start=1):
print(f"{rank}. {name}: {grade}")Salida:
1. Alice: B
2. Carol: A
3. Dave: DCada paso produce un iterador; nada se materializa en una lista hasta que el bucle for lo consume. Esto mantiene el uso de memoria constante independientemente del tamaño de la entrada.
Errores comunes
Los iteradores se agotan después de un recorrido
map(), filter() y zip() devuelven iteradores, no listas. Una vez que consumes un iterador, queda vacío:
squares = map(lambda x: x ** 2, [1, 2, 3])
print(list(squares)) # [1, 4, 9]
print(list(squares)) # [] — already exhaustedEnvuelve en list() desde el principio si necesitas iterar más de una vez.
zip() descarta silenciosamente los elementos sobrantes
Cuando las secuencias de entrada tienen longitudes diferentes, zip() descarta los elementos extra sin advertencia. Verifica siempre las longitudes explícitamente si esto importa, o usa itertools.zip_longest.
enumerate() no modifica el iterable
enumerate() envuelve el iterable original; no lo copia. Modificar la lista subyacente durante la iteración puede producir resultados inesperados — la misma precaución que aplica a los bucles for ordinarios.
Referencia rápida
| Función | Firma | Qué devuelve |
|---|---|---|
enumerate | enumerate(iterable, start=0) | Iterador de tuplas (índice, valor) |
zip | zip(*iterables) | Iterador de tuplas, un elemento de cada iterable por tupla |
map | map(function, *iterables) | Iterador de function aplicada a cada elemento |
filter | filter(function, iterable) | Iterador de elementos donde function devuelve True |
Las cuatro son perezosas: producen valores bajo demanda en lugar de construir una lista completa de antemano.