Skip to content
This page has been auto-translated and may contain errors.View in English

Lambdas y comprensiones

docs.scrimba.com

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.

Lambdas, comprensiones y zip son tres herramientas que comprimen patrones comunes en expresiones. No son requeridas, pero aparecen en todo el código Python y vale la pena reconocerlas y escribirlas con soltura. El principio rector: úsalas cuando hagan la intención más clara, no solo más corta.

Estas herramientas expresan una transformación como una expresión (un fragmento de código que se evalúa a un valor) en lugar de un bucle imperativo que construye un resultado paso a paso. Las lambdas te dan una pequeña función sin nombre inline. Las comprensiones construyen una colección en un solo pasaje. Los generadores son perezosos (producen valores bajo demanda en lugar de todos a la vez), así que mantienen un elemento en memoria en lugar de toda la secuencia. zip camina varias secuencias al paso. La recompensa es código que se lee como "qué quiero" en lugar de "cómo ensamblarlo", siempre que dejes de escribir antes de que el one-liner deje de ser legible.

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().

Una lambda es una función anónima de una sola expresión. Puede tomar múltiples argumentos pero su cuerpo debe ser una única expresión, no una declaración. Su uso principal es como argumento inline key= o callback donde un def completo agregaría indirección innecesaria. Para cualquier cosa más compleja, usa def.

Una lambda es el mismo tipo de objeto función que produce def, con tres límites: no tiene nombre (aparece como <lambda> en un traceback, el reporte de errores que Python imprime cuando algo lanza), su cuerpo es una expresión así que no puede contener declaraciones, y no lleva docstring. La trampa que cuesta tiempo real de depuración es late binding en un closure (una función que captura una variable del scope a su alrededor). Una lambda construida dentro de un bucle captura la variable del bucle por referencia, así que cada lambda ve el valor final de la variable, no el valor que tenía cuando se creó la lambda:

python
funcs = [lambda: i for i in range(3)]
print([f() for f in funcs])        # [2, 2, 2], no [0, 1, 2]

funcs = [lambda i=i: i for i in range(3)]
print([f() for f in funcs])        # [0, 1, 2]: i=i vincula el valor ahora

El truco i=i fija el valor actual como un argumento por defecto en el momento de la creación. Recurre a él cada vez que construyas callables en un bucle.

python
double = lambda x: x * 2
double(5)   # 10

Esto es equivalente a:

python
def double(x):
    return x * 2

En 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():

python
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:

python
add = lambda a, b: a + b
add(3, 4)   # 7

Cuá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.

JunoFunciones lambda Una lambda es una función tiny de una línea sin nombre, escrita con la palabra clave 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.
JunoFunciones lambda Una lambda es una función anónima de una sola expresión, útil como key= inline o callback donde un def nombrado solo agregaría ruido. El cuerpo es una expresión, sin declaraciones permitidas. Cualquier cosa más involucrada, recurre a def.
JunoFunciones lambda Mismo objeto función que def, menos un nombre, declaraciones y un docstring, así que mantenlas a un key= de una línea. La trampa es late binding: una lambda construida en un bucle lee el valor final de la variable del bucle, así que escribe lambda i=i: i para fijarlo. Ese solo muerde a todos exactamente una vez.

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.

Las comprensiones de lista son un reemplazo conciso para el patrón de construir-con-un-bucle. Generalmente son más rápidas que el bucle for equivalente con .append(), porque Python no llama a un método en cada pasada. La estructura es [expression for item in iterable if condition]; la cláusula if es opcional.

Una comprensión es significativamente más rápida que el mismo bucle llamando a .append() en cada pasada, así que es la forma idiomática de construir una lista desde una transformación. También se ejecuta en su propio scope (la región donde un nombre es visible), lo que significa que la variable del bucle no se filtra al código circundante: después de [n ** 2 for n in numbers], el nombre n no existe fuera de la comprensión. La trampa a cuidar es recurrir a una donde el cuerpo hace trabajo real. Una vez que la expresión crece más allá de una transformación limpia, o querrías agregar logging o un bloque try, el bucle explícito es la herramienta mejor, porque una comprensión solo puede sostener una expresión, nunca una declaración.

La forma larga:

python
numbers = [1, 2, 3, 4, 5]
squares = []
for n in numbers:
    squares.append(n ** 2)

La comprensión de lista:

python
squares = [n ** 2 for n in numbers]

La estructura es siempre la misma: [expression for item in iterable].

python
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
JunoComprensiones de listas[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.
JunoComprensiones de listas[expression for item in iterable] reemplaza el patrón de construir-con-un-bucle-y-.append(), y se lee más limpio haciéndolo. Produce una lista nueva y no muta nada. Mantén la expresión simple, o la legibilidad que ganaste se va.
JunoComprensiones de listas Más rápido que el bucle .append() y se ejecuta en su propio scope, así que la variable del bucle nunca se filtra. La línea a mantener: una comprensión toma una expresión, no una declaración, así que en el segundo en que quieras un bloque try o una línea de log, el bucle explícito gana.

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.

La cláusula if en una comprensión es un filtro, no un if/else. Se ejecuta una vez por elemento e incluye solo elementos para los que la condición es truthy. Para una transformación condicional (mapear un valor a otro basado en una condición), usa una expresión ternaria dentro de la expresión principal.

La posición del if decide si filtras o transformas, y mezclarlos es una causa común de un resultado de longitud incorrecta. Un if al final filtra: [x for x in data if x > 0] descarta elementos. Un condicional al frente mapea: [x if x > 0 else 0 for x in data] mantiene cada elemento y reescribe los que fallan (aquí, limitando negativos a cero). Puedes combinar ambos en una comprensión, [x * 2 for x in data if x > 0], y apilar múltiples cláusulas if finales, que se encadenan como and.

python
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]
python
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]
JunoFiltrar con una condición Agrega 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.
JunoFiltrar con una condición Un `if` final filtra, manteniendo solo elementos donde la condición es truthy. Eso es diferente de un condicional dentro de la expresión: [x if x > 0 else 0 for x in data] reescribe valores en lugar de descartarlos. Sabe cuál quieres antes de escribirlo.
JunoFiltrar con una condiciónif al final filtra y cambia la longitud; un condicional al frente mapea y la mantiene. Confunde los dos y envías un resultado que es del tamaño equivocado. Las cláusulas `if` finales apiladas se encadenan con and.

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.

Las comprensiones anidadas se ejecutan de izquierda a derecha. La primera cláusula for es el bucle exterior, la segunda es el bucle interior. Producen un resultado plano único, no una estructura 2D. Si la comprensión es difícil de leer de un vistazo, escribe los bucles explícitamente.

El orden de la cláusula se lee en el mismo orden que los bucles anidados equivalentes: el primer for es el bucle exterior, el segundo es el interior, y una cláusula posterior puede usar un nombre vinculado por una anterior. Donde esto gana puntos es al aplanar; donde duele es una verdadera grilla de combinaciones (cada par de dos secuencias), para la cual itertools.product se lee mucho más claro que un for doble. La regla que mantiene un codebase cuerdo: si analizar la comprensión te toma más de un segundo, el bucle explícito es la mejor documentación, así que escribe eso en su lugar.

python
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.

JunoComprensiones anidadas Dos cláusulas 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.
JunoComprensiones anidadas Apila dos cláusulas for y la primera es el bucle exterior, la segunda el interior: [item for row in matrix for item in row] aplana una lista 2D en una. El resultado es plano, no una grilla. Cuando deja de ser legible de un vistazo, los bucles explícitos son mejores.
JunoComprensiones anidadas El primer for es el bucle exterior, y una cláusula posterior puede usar un nombre de una anterior. Excelente para aplanar, pobre para combinaciones de cada par, donde itertools.product se lee más limpio. Si analizarla toma más de un segundo, el bucle es la mejor documentación.

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.

Las comprensiones de diccionario crean un dict nuevo desde cualquier iterable que produzca pares clave-valor. La sintaxis es {key_expr: val_expr for item in iterable if condition}. Las claves duplicadas del bucle usan el valor más reciente, silenciosamente. .items() en un dict existente es la fuente iterable más común para comprensiones de diccionario.

Dos cosas muerden en código real. Primero, cada clave tiene que ser hashable (un valor que Python puede reducir a un número de búsqueda fijo, lo que significa que nunca cambia, así que strings y números y tuplas califican pero listas y dicts no). Una expresión de clave que produce una lista lanza TypeError. Segundo, las claves duplicadas no dan error: si el bucle produce la misma clave dos veces, el valor posterior silenciosamente sobrescribe el anterior, que es una fuente silenciosa de datos perdidos cuando construyes un dict desde una fuente que no es realmente única. Cuando solo quieres fusionar dos dicts existentes, el operador | (la forma de fusión de dict, Python 3.9 y arriba) expresa la intención más claramente que una comprensión.

python
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:

python
passing = {name: score for name, score in score_map.items() if score >= 80}
# {"maría": 87, "laura": 92}
python
words = ["manzana", "plátano", "cereza"]
word_lens = {word: len(word) for word in words}
# {"manzana": 7, "plátano": 7, "cereza": 6}
JunoComprensiones de diccionarios{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.
JunoComprensiones de diccionarios{key: value for item in iterable} crea un dict desde cualquier fuente de pares. Las dos fuentes cotidianas son .items() en un dict existente y zip() sobre dos listas. Cuidado con las claves duplicadas: la última gana, silenciosamente.
JunoComprensiones de diccionarios Las claves deben ser hashable (sin listas), y las claves duplicadas no dan error, el valor más reciente sobrescribe silenciosamente, lo que come datos cuando tu fuente no es única. Así que verifica unicidad antes de confiar en el conteo. Para fusionar dos dicts, | lo dice más claramente que una comprensión.

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.

Las comprensiones de conjunto usan {expression for item in iterable} y producen un set. Desduplican automáticamente. Úsalas cuando necesites una colección única construida desde una transformación, donde el orden no importa.

El resultado es un set, así que dos cosas siguen que la versión de lista no te da. Los valores duplicados de la expresión colapsan en uno automáticamente, que es el punto: es la forma más limpia de desduplicar una transformación en una sola expresión. El costo es que un conjunto no tiene orden, así que no puedes confiar en que los elementos salgan en ninguna secuencia particular. Los miembros también tienen que ser hashable, la misma regla que se aplica a las claves de dict: un valor que Python puede reducir a un número de búsqueda fijo, lo que excluye listas.

python
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.

JunoComprensiones de conjuntos{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.
JunoComprensiones de conjuntos{expr for item in iterable} produce un set, desduplicando mientras va. Recurre a esto cuando quieras una colección única desde una transformación y el orden no importa. Llaves sin dos puntos, esa es la única cosa que la separa de una comprensión de diccionario.
JunoComprensiones de conjuntos El conjunto descarta duplicados automáticamente, que es la razón toda de elegirlo sobre una comprensión de lista. El trueque es sin orden en el que confiar, y los miembros deben ser hashable, así que sin listas adentro. Mejor ajuste: desduplicar una transformación en una expresión.

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.

Una expresión generadora produce un iterador, no una colección. Computa valores perezosamente: el siguiente valor solo se produce cuando se solicita. Esto es más valioso cuando el resultado se consume inmediatamente por una función como sum(), max() o any(), así que no hay punto en construir la lista completa primero.

Un generador produce valores perezosamente (uno a la vez, solo cuando algo pide el siguiente), así que su memoria se mantiene plana sin importar cuán grande sea la entrada: nunca sostiene más que el valor actual, donde una comprensión de lista sostiene cada elemento a la vez. Eso lo hace la llamada correcta para una fuente grande o streaming alimentando directamente en sum(), max() o any(). El modo de fallo a recordar es que un generador es de un solo uso: una vez que algo lo itera hasta el final, está exhausto y no produce nada más, así que un segundo bucle sobre el mismo generador corre cero veces y no da error. Si necesitas caminar los datos dos veces, construye una lista una vez e itera esa.

python
squares_gen = (n ** 2 for n in range(1000000))
python
total = sum(n ** 2 for n in range(1000000))   # sum() consume el generador

Al pasar un generador directamente a una función como sum(), max(), min() o any(), puedes descartar los paréntesis extra:

python
total = sum(n ** 2 for n in range(1000))   # un conjunto de parens, no dos

Para 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.

JunoExpresiones generadoras Un generador se ve como una comprensión de lista con paréntesis en lugar de corchetes, pero hace valores uno a la vez en lugar de construir la lista completa al frente. Para una secuencia gigante eso salva un montón de memoria. El caso ordenado: suelta uno directamente en sum() o max() y salta construir la lista del todo.
JunoExpresiones generadoras Una expresión generadora devuelve un iterador perezoso: computa el siguiente valor solo cuando se pide, así nada construye la lista completa en memoria. Mejor cuando el resultado alimenta directamente en sum(), max() o any(). Recuerda que es de un solo uso, una vez consumido está vacío.
JunoExpresiones generadoras Perezoso y plano en memoria sin importar cuán grande sea la entrada, así que es la herramienta correcta para una fuente grande o streaming alimentando sum() o any(). El truco: es de un solo uso, y un segundo bucle sobre un generador exhausto corre cero veces sin error. ¿Necesitas dos pasadas? Construye una lista una vez.

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í.

zip() devuelve un iterador perezoso de tuplas, consumiendo sus iterables de entrada al paso. Se detiene en la entrada más corta: las secuencias más largas se truncan silenciosamente. Para secuencias que pueden diferir en longitud, itertools.zip_longest() rellena las más cortas con un valor especificado.

zip() camina sus entradas al paso y se detiene en el momento en que la más corta se agota. Ese truncamiento es la trampa a diseñar alrededor: empareja una lista de 1000 registros con una de 999 y silenciosamente pierdes el último registro, sin error, sin advertencia. Cuando las longitudes se supone que deben coincidir, zip(seq_a, seq_b, strict=True) (Python 3.10 y arriba) lanza si difieren en lugar de descartar datos, e itertools.zip_longest rellena los huecos cuando se permite que difieran. El otro truco vale la pena saber: zip(*rows) transpone, convirtiendo una lista de filas en una lista de columnas, porque * desempaqueta la lista exterior en argumentos separados.

python
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: 92

zip() 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):

python
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:

python
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})")
Junozip()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.
Junozip()zip() devuelve un iterador perezoso de tuplas, pasando por sus entradas en paralelo. Se detiene en la más corta, así que las secuencias más largas se truncan sin un sonido. itertools.zip_longest() rellena los huecos cuando las longitudes legítimamente difieren, y zip(*pairs) desempareja.
Junozip() Perezoso, paralelo, y se detiene en la entrada más corta, lo que silenciosamente descarta datos cuando las longitudes se suponía que coincidían. Pasa strict=True para hacer que una discrepancia lance en su lugar. zip(*rows) es tu transpuesta, columnas de filas en una llamada.

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.

map(func, iterable) devuelve un iterador perezoso que aplica func a cada elemento. filter(func, iterable) devuelve un iterador perezoso de elementos para los que func es truthy. Ambos preexisten a las comprensiones. Prefiere comprensiones en código nuevo; usa map() cuando ya tengas una función nombrada que haga lo que necesitas.

Ambos devuelven iteradores perezosos, no listas, así que los envuelves en list() cuando quieres los valores ahora. Se mapean uno a uno en las comprensiones que ya conoces: map(f, it) es (f(x) for x in it), y filter(pred, it) es (x for x in it if pred(x)). Esa equivalencia es la regla de decisión. Con una lambda inline, la comprensión se lee mejor y es el estándar moderno. Con una función nombrada que ya hace el trabajo, list(map(int, strings)) se lee como "mapear int sobre strings" y es la opción más limpia, así que la única llamada real es si ya tienes una función a mano.

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:

python
strings = ["1", "2", "3"]
numbers = list(map(int, strings))   # [1, 2, 3] (más limpio que una comprensión aquí)
Junomap() y filter()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.
Junomap() y filter()map() transforma cada elemento, filter() mantiene los truthy, ambos perezosos así que envuelve en list() para ver resultados. Las comprensiones son el estándar para código nuevo. El único lugar donde map() gana es una función nombrada que ya tienes: map(int, strings) se lee más limpio que la comprensión.
Junomap() y filter()map(f, it) es (f(x) for x in it) y filter(pred, it) es (x for x in it if pred(x)), ambos perezosos. Así que la opción es puramente legibilidad: con una lambda inline, la comprensión gana; con una función ya nombrada, map(int, strings) gana.

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:

python
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}")

Filtra una lista de usuarios para administradores activos, construye un dict de id a nombre, y recopila nombres ordenados en un pasaje cada uno:

python
raw_users = [
    {"id": 1, "name": "María", "role": "admin", "active": True},
    {"id": 2, "name": "Juan", "role": "user", "active": False},
    {"id": 3, "name": "Laura", "role": "admin", "active": True},
    {"id": 4, "name": "Carlos", "role": "user", "active": True},
]

active_admins = [u for u in raw_users if u["active"] and u["role"] == "admin"]
id_map = {u["id"]: u["name"] for u in raw_users}
names = sorted(u["name"] for u in raw_users if u["active"])

print(f"Active admins: {[u['name'] for u in active_admins]}")
print(f"All active: {names}")

Empareja nombres de características con puntuaciones de importancia usando zip, construye una comprensión de dict, ordena con una lambda, y normaliza valores en una segunda comprensión:

python
feature_names = ["age", "income", "score", "tenure"]
importances = [0.12, 0.34, 0.28, 0.26]

feat_dict = {f: i for f, i in zip(feature_names, importances)}
top_feats = sorted(feat_dict.items(), key=lambda x: x[1], reverse=True)[:2]

print("Top 2 features:")
for name, score in top_feats:
    print(f"  {name}: {score:.2f}")

# Normalizar a suma de 1.0 (los valores ya suman 1 aquí, pero se muestra como un patrón)
total = sum(feat_dict.values())
normalised = {k: round(v / total, 4) for k, v in feat_dict.items()}
print(f"Normalised: {normalised}")

zip empareja las dos listas sin construir tuplas intermedias. La comprensión de dict construye el mapeo en una expresión. El lambda de ordenamiento evita una función de clave nombrada. La comprensión de normalización transforma valores sin mutar el dict original.