Lambdas y comprensiones

Estas tres características tienen algo en común: te permiten expresar ideas que de otro modo ocuparían varias líneas en una única expresión legible. Usadas bien, hacen el código más corto y claro. Usadas mal, lo hacen ilegible. Este capítulo cubre cuándo recurrir a cada una y cuándo parar.
Funciones lambda
Una lambda es una función sin nombre que tiene una sola expresión. La creas con la palabra clave lambda. Su verdadera utilidad es que puedes escribirla inline, justo donde la necesitas, sin definir una función nombrada primero. Esto es lo que la hace útil con sorted().
double = lambda x: x * 2
double(5) # 10Esto es equivalente a:
def double(x):
return x * 2En la mayoría de los casos, usa def. Las lambdas tienen una verdadera ventaja: puedes escribirlas inline, justo donde las necesitas, sin nombrarlas. Esto es lo que las hace útiles con sorted(), map() y filter():
players = [("María", 87), ("Juan", 74), ("Laura", 92)]
sorted(players, key=lambda p: p[1]) # ordenar por puntuación (ascendente)
sorted(players, key=lambda p: p[1], reverse=True) # ordenar por puntuación (descendente)Sin una lambda, tendrías que definir una función nombrada solo para el argumento key=. La lambda mantiene la intención local y visible.
Las lambdas pueden tomar múltiples argumentos:
add = lambda a, b: a + b
add(3, 4) # 7Cuándo usar una lambda: solo cuando es una pequeña expresión usada en un lugar. Si está creciendo, o necesitas reutilizarla, escribe un def propio. Una lambda que abarca varios operadores o necesita un condicional es usualmente una señal de cambiar a def.
lambda. Su razón toda de existir es ir inline como key= para sorted() así no defines una función separada para un trabajo. En el momento en que se hace más larga que una expresión limpia, escribo un def real y me siento mejor por ello. Comprensiones de listas
La transformación más común en Python: toma una secuencia, haz algo con cada elemento, obtén una nueva lista de vuelta. Una comprensión de lista hace esto en una línea legible: [expression for item in iterable]. También puedes agregar un filtro con if.
La forma larga:
numbers = [1, 2, 3, 4, 5]
squares = []
for n in numbers:
squares.append(n ** 2)La comprensión de lista:
squares = [n ** 2 for n in numbers]La estructura es siempre la misma: [expression for item in iterable].
scores = [87, 42, 96, 55, 71]
scaled = [s * 1.1 for s in scores] # aplicar un bono del 10%
as_grades = [f"{s}/100" for s in scores] # formatear cada uno[expression for item in iterable] toma una secuencia, hace una cosa con cada elemento, y te devuelve una lista nueva. Léelo de izquierda a derecha y dice exactamente qué hace. Esta fue la primera característica de Python que me hizo sentir que estaba escribiendo Python en lugar de traducir desde otro lenguaje. Filtrar con una condición
Agrega una cláusula if para incluir solo elementos que pasen una prueba. El resultado es una nueva lista con solo los elementos donde la condición es True.
numbers = [1, 2, 3, 4, 5, 6, 7, 8]
evens = [n for n in numbers if n % 2 == 0] # [2, 4, 6, 8]
odds = [n for n in numbers if n % 2 != 0] # [1, 3, 5, 7]scores = [87, 42, 96, 55, 71, 38]
passing = [s for s in scores if s >= 60] # [87, 96, 71]
failing = [s for s in scores if s < 60] # [42, 55, 38]if condition al final para mantener solo los elementos que pasen la prueba: [x for x in data if x > 0]. Cualquier cosa que salga falsy se deja fuera de la lista nueva. Misma comprensión que ya conoces, con un portero en ella. Comprensiones anidadas
Puedes anidar comprensiones para aplanar una lista de listas en una sola lista. Léelo de izquierda a derecha: para cada fila, para cada elemento en esa fila, incluye el elemento.
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [item for row in matrix for item in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]Léelo de izquierda a derecha: para cada fila en matrix, para cada elemento en fila, incluye element.
Las comprensiones anidadas pueden volverse confusas rápidamente. Si toma más de un momento analizarla, escribe los bucles explícitamente.
for en una comprensión aplanan una lista de listas en una sola lista plana: [item for row in matrix for item in row]. Léelo de izquierda a derecha, bucle exterior primero, exactamente el orden en que escribirías los bucles. Si tus ojos se enganchan en ello, esa es tu señal de escribir los bucles reales en su lugar. Comprensiones de diccionarios
Las comprensiones de diccionario construyen un diccionario en una expresión, usando la misma idea que las comprensiones de lista: {key: value for item in iterable}. Agrega un filtro con if, igual que con las comprensiones de lista.
names = ["maría", "juan", "laura"]
scores = [87, 74, 92]
score_map = {name: score for name, score in zip(names, scores)}
# {"maría": 87, "juan": 74, "laura": 92}Con un filtro:
passing = {name: score for name, score in score_map.items() if score >= 80}
# {"maría": 87, "laura": 92}words = ["manzana", "plátano", "cereza"]
word_lens = {word: len(word) for word in words}
# {"manzana": 7, "plátano": 7, "cereza": 6}{key: value for item in iterable} construye un diccionario en una línea, misma forma que una comprensión de lista con dos puntos entre clave y valor. Emparejalo con .items() para remodelar un dict que ya tienes, o con zip() para coser dos listas en un mapeo. Agrega un if al final para mantener solo los pares que quieres. Comprensiones de conjuntos
Las comprensiones de conjunto construyen un conjunto en una expresión, con llaves y sin dos puntos. Porque el resultado es un conjunto, los duplicados se eliminan automáticamente.
words = ["manzana", "plátano", "cereza", "manzana"]
unique = {w.lower() for w in words} # {"manzana", "plátano", "cereza"}Usa comprensiones de conjunto cuando quieras valores únicos y no te importe el orden.
{expr for item in iterable} con llaves y sin dos puntos construye un conjunto, y un conjunto descarta duplicados gratis. Así que si tu trabajo es "dame los únicos", esto lo hace en una línea. No cuentes con ningún orden particular viniendo de vuelta aunque. Expresiones generadoras
Los generadores se ven como comprensiones de lista con paréntesis en lugar de corchetes. La diferencia clave: una comprensión de lista construye la lista completa en memoria a la vez. Un generador produce valores uno a la vez, solo cuando es necesario. Para secuencias grandes, esto usa mucha menos memoria.
squares_gen = (n ** 2 for n in range(1000000))total = sum(n ** 2 for n in range(1000000)) # sum() consume el generadorAl pasar un generador directamente a una función como sum(), max(), min() o any(), puedes descartar los paréntesis extra:
total = sum(n ** 2 for n in range(1000)) # un conjunto de parens, no dosPara la mayoría del código cotidiano, las comprensiones de lista están bien. Usa generadores cuando estés procesando datasets grandes o datos streaming donde sostener todo en memoria sería derrochador.
sum() o max() y salta construir la lista del todo. zip()
zip() empareja elementos de dos o más secuencias juntos para que puedas recorrerlas en paralelo. Se detiene en la secuencia más corta. Es la forma limpia de evitar manejar índices cuando dos listas se corresponden entre sí.
names = ["María", "Juan", "Laura"]
scores = [87, 74, 92]
for name, score in zip(names, scores):
print(f"{name}: {score}")
# María: 87
# Juan: 74
# Laura: 92zip() se detiene en la secuencia más corta. Si tus secuencias pueden tener longitudes diferentes, usa itertools.zip_longest() con un valor de relleno.
Para convertir de vuelta desde una lista zipeada de pares en dos listas separadas, usa zip(*pairs):
pairs = [("María", 87), ("Juan", 74), ("Laura", 92)]
names, scores = zip(*pairs)
# names = ("María", "Juan", "Laura")
# scores = (87, 74, 92)*pairs desempaqueta la lista en argumentos separados, así que zip(*pairs) se convierte en zip(("María", 87), ("Juan", 74), ("Laura", 92)). El operador * se cubre en el capítulo Funciones.
zip() es también la forma limpia de iterar múltiples secuencias en paralelo sin manejar índices manualmente:
before = [10, 20, 30]
after = [15, 18, 35]
for b, a in zip(before, after):
change = a - b
print(f"{b} -> {a} ({'+' if change >= 0 else ''}{change})")zip() empareja dos o más secuencias para que las recorras juntas, sin malabarismo de índices. Se detiene en la más corta, así que longitudes no coincidentes silenciosamente pierden los extras. Y zip(*pairs) lo ejecuta en reversa, dividiendo una lista de tuplas de vuelta en listas separadas. map() y filter()
map() y filter() son herramientas de estilo funcional más antiguas que hacen lo que hacen las comprensiones. Las verás en código más antiguo, así que vale la pena saber qué significan. Prefiere comprensiones para código nuevo; son más legibles para la mayoría de desarrolladores de Python.
numbers = [1, 2, 3, 4, 5]
list(map(lambda x: x ** 2, numbers)) # [1, 4, 9, 16, 25]
list(filter(lambda x: x % 2 == 0, numbers)) # [2, 4]Prefiere comprensiones; son más legibles para la mayoría de desarrolladores de Python. Usa map() cuando tengas una función nombrada que ya existe:
strings = ["1", "2", "3"]
numbers = list(map(int, strings)) # [1, 2, 3] (más limpio que una comprensión aquí)map(func, iterable) ejecuta una función sobre cada elemento; filter(func, iterable) mantiene solo los elementos donde la función devuelve truthy. Son la forma más antigua de hacer lo que hacen las comprensiones, así que los encontrarás en el código de otras personas. Para el tuyo, una comprensión se lee más claro para la mayoría de gente. En la práctica
Filtra una lista de jugadores a puntuaciones aprobadas, ordena por puntuación con sorted y una lambda, luego imprime con posiciones enumeradas:
players = [
{"name": "María", "score": 87},
{"name": "Juan", "score": 42},
{"name": "Laura", "score": 96},
{"name": "Carlos", "score": 55},
]
passing = [p for p in players if p["score"] >= 60]
ranked = sorted(passing, key=lambda p: p["score"], reverse=True)
score_map = {p["name"]: p["score"] for p in ranked}
for i, (name, score) in enumerate(score_map.items(), start=1):
print(f"{i}. {name}: {score}")
