Ana quiere ver el catálogo de Papyrus ordenado por precio, saber cuál es el libro más barato y filtrar los que tienen stock. Todas esas operaciones tienen algo en común: una función principal (sorted(), min(), un filtro) necesita que le digas con qué criterio trabajar — y ese criterio suele ser una función diminuta, de una sola expresión, que no merece un def con nombre, docstring y hueco propio en el archivo. Para esos casos Python ofrece las funciones lambda: funciones anónimas de una línea que se escriben justo donde se usan. En esta lección aprenderás su sintaxis, cuándo son una buena idea y cuándo no (PEP 8 tiene opinión firme), y las pondrás a trabajar con sorted(), min()/max(), map() y filter() — comparando estas dos últimas con las comprensiones del módulo 2, que a menudo las superan.
Contenido
- Sintaxis: anatomía de una lambda
- Lambda frente a
def: cuándo sí y cuándo no - El parámetro
key: ordenar consorted() min()ymax()con criteriomap()yfilter()frente a comprensiones- Proyecto: el catálogo de Papyrus ordenado por precio
Sintaxis: anatomía de una lambda
Una lambda es una función sin nombre reducida a su mínima expresión:
Comparémosla con su def equivalente:
def precio_con_iva(base):
return round(base * 1.04, 2)
# La misma lógica como lambda:
lambda base: round(base * 1.04, 2)Correspondencia pieza a pieza:
def |
lambda |
|---|---|
def precio_con_iva(base): |
lambda base: |
| Nombre de la función | No tiene (es anónima) |
Cuerpo de varias líneas, con if, bucles, etc. |
Una única expresión |
return explícito |
Implícito: la expresión ES el valor devuelto |
| Docstring | No admite |
Restricción clave: el cuerpo debe ser una expresión — algo que produce un valor. Caben operaciones, llamadas a funciones, f-strings y el ternario del módulo 2 ("agotado" if stock == 0 else "disponible"), pero no sentencias: nada de asignaciones, while, for como bucle ni varias líneas. Si necesitas eso, necesitas un def.
Una lambda puede tener varios parámetros (lambda titulo, precio: f"{titulo}: {precio} €") e incluso valores por defecto, aunque en la práctica la inmensa mayoría reciben uno o dos.
Lambda frente a def: cuándo sí y cuándo no
Puedes ejecutar una lambda directamente, e incluso guardarla en una variable:
print((lambda base: round(base * 1.04, 2))(12.50)) # 13.0 — legal, pero rebuscado
con_iva = lambda base: round(base * 1.04, 2) # legal, pero PEP 8 dice NOEsa segunda línea funciona... y PEP 8 la desaconseja explícitamente: si le vas a dar nombre, usa def. Los motivos son prácticos:
- Con
def con_iva(base): ..., los errores y las herramientas de depuración muestran el nombrecon_iva; una lambda asignada aparece como el anónimo<lambda>, más difícil de rastrear. defadmite docstring, varias líneas y crece sin reescrituras.- No se gana nada:
con_iva = lambda ...ocupa lo mismo que eldefy comunica peor.
Entonces, ¿para qué sirven? Para pasarlas como argumento a otra función, escritas en el punto exacto donde se usan. Esa es su casa:
| Situación | Herramienta |
|---|---|
Criterio de una línea para sorted/min/max/map/filter, usado una sola vez |
lambda |
| La misma lógica se usa en dos o más sitios | def con nombre |
El criterio necesita if/else de verdad, validación o varias líneas |
def |
| Quieres documentar qué hace | def (docstring) |
| Asignarla a un nombre "para abreviar" | Nunca: def |
Que una función se pueda pasar como argumento no debería sorprenderte a estas alturas: en Python las funciones son valores, como los números o las cadenas. buscar_libro (sin paréntesis, lección 03-01) es un valor que nombra una función; una lambda es lo mismo, pero sin pasar por un nombre.
El parámetro key: ordenar con sorted()
sorted(secuencia) devuelve una lista nueva ordenada (la original no cambia):
print(sorted(precios)) # [9.95, 12.5, 15.9]
print(sorted(catalogo)) # ['El Quijote', 'Hamlet', 'La Odisea'] (orden alfabético)Pero Ana no quiere ordenar precios sueltos ni títulos: quiere ordenar libros por precio. Primero emparejamos título y precio con zip() (lección anterior) y luego entra el argumento keyword key: una función que, aplicada a cada elemento, produce el valor por el que ordenar.
libros = list(zip(catalogo, precios))
# [('La Odisea', 12.5), ('Hamlet', 9.95), ('El Quijote', 15.9)]
por_precio = sorted(libros, key=lambda libro: libro[1])
# [('Hamlet', 9.95), ('La Odisea', 12.5), ('El Quijote', 15.9)]Cómo funciona por dentro: para cada tupla libro, sorted() llama a la lambda, obtiene libro[1] (el precio) y ordena las tuplas comparando esos valores. La lambda no ordena nada; solo responde a la pregunta "¿por cuál de tus datos te comparo?".
Variantes útiles:
mas_caros = sorted(libros, key=lambda libro: libro[1], reverse=True) # descendente
alfabetico = sorted(catalogo, key=lambda t: t.lower()) # ignora mayúsculasreverse=True invierte el orden — otro argumento keyword, como los de la lección 03-02. Y key=lambda t: t.lower() resuelve el clásico problema de que las mayúsculas alteren el orden alfabético.
min() y max() con criterio
min() y max() aceptan el mismo key, y responden preguntas de negocio en una línea:
mas_barato = min(libros, key=lambda libro: libro[1])
mas_caro = max(libros, key=lambda libro: libro[1])
print(f"Chollo del día: {mas_barato[0]} a {mas_barato[1]:.2f} €")
# Chollo del día: Hamlet a 9.95 €
print(f"Joya de la tienda: {mas_caro[0]} a {mas_caro[1]:.2f} €")
# Joya de la tienda: El Quijote a 15.90 €Detalle importante: devuelven el elemento completo (la tupla título-precio), no el valor del criterio. Por eso mas_barato[0] da el título. Sin key, min(precios) daría 9.95 — el precio, pero sin saber de qué libro.
map() y filter() frente a comprensiones
Dos funciones clásicas que reciben otra función como argumento:
map(funcion, secuencia): aplica la función a cada elemento (transforma).filter(funcion, secuencia): conserva los elementos para los que la función devuelve verdadero (criba).
Ambas son perezosas, como zip(): entregan resultados a medida que se piden, y list() los materializa.
# Transformar: precios de socio de todo el catálogo
list(map(lambda base: precio_final(base, socio=True), precios))
# [12.35, 9.83, 15.71]
# Cribar: precios por debajo de 13 €
list(filter(lambda base: base < 13, precios))
# [12.5, 9.95]¿Te suenan estas operaciones? Son exactamente lo que hacías en el módulo 2 con comprensiones:
[precio_final(base, socio=True) for base in precios] # map
[base for base in precios if base < 13] # filterComparativa honesta:
map()/filter() + lambda |
Comprensión | |
|---|---|---|
| Legibilidad con lógica propia | Regular: la lambda añade ruido | Alta: se lee como una frase |
| Con una función ya existente | Muy buena: map(str.strip, lineas) |
Buena: [l.strip() for l in lineas] |
| Transformar y cribar a la vez | Torpe: hay que anidar map(f, filter(g, x)) |
Natural: [f(x) for x in xs if g(x)] |
| Resultado | Perezoso (hay que hacer list()) |
Lista directamente |
| Estilo dominante en Python moderno | En retroceso | Recomendado |
Criterio para el curso: usa comprensiones por defecto; recurre a map()/filter() cuando la función ya exista y tenga nombre (ahí ni siquiera hace falta lambda). Aun así, debes saber leerlas: te las encontrarás constantemente en código ajeno.
Proyecto: el catálogo de Papyrus ordenado por precio
Juntemos todo en tarifa.py: la tabla del catálogo ordenada por precio, con los precios de socio de Luis y un pequeño informe:
IVA_LIBROS = 0.04
DESCUENTO_SOCIO = 0.05
catalogo = ["La Odisea", "Hamlet", "El Quijote"]
precios = [12.50, 9.95, 15.90]
stocks = [4, 6, 8]
def precio_final(base, socio=False, iva=IVA_LIBROS):
"""Precio final: descuento de socio (si procede) y luego IVA."""
if socio:
base = base * (1 - DESCUENTO_SOCIO)
return round(base * (1 + iva), 2)
libros = list(zip(catalogo, precios, stocks))
por_precio = sorted(libros, key=lambda libro: libro[1])
print(f"{'Título':<12} {'Base':>7} {'Socio':>7}")
print("-" * 28)
for titulo, base, stock in por_precio:
print(f"{titulo:<12} {base:>5.2f} € {precio_final(base, socio=True):>5.2f} €")
mas_barato = min(libros, key=lambda libro: libro[1])
print(f"\nRecomendación económica: {mas_barato[0]} ({mas_barato[1]:.2f} €)")Título Base Socio ---------------------------- Hamlet 9.95 € 9.83 € La Odisea 12.50 € 12.35 € El Quijote 15.90 € 15.71 € Recomendación económica: Hamlet (9.95 €)
Repasa el reparto de papeles: zip() empaqueta las listas paralelas en tuplas por libro, la lambda de key señala el precio como criterio, sorted() ordena sin tocar los datos originales y precio_final() — nuestra función con nombre, porque se reutiliza — calcula la columna de socios. Cada herramienta en su sitio.
Errores Comunes y Consejos
- Asignar lambdas a nombres (
f = lambda x: ...): funciona, pero PEP 8 lo veta y las trazas de error mostrarán<lambda>en vez de un nombre útil. Usadef. - Intentar meter sentencias en una lambda (
lambda x: y = x + 1o varias líneas):SyntaxError. Una lambda es una expresión; si no cabe, es undef. - Pasar la lambda llamada en vez de la lambda:
sorted(libros, key=libro[1])falla —keyespera una función, no un valor. La forma correcta eskey=lambda libro: libro[1]. - Olvidar
list()conmap()/filter():print(map(...))muestra<map object at 0x...>, no los datos. Materialízalo conlist()o recórrelo confor. - Lambdas kilométricas: si tu lambda tiene un ternario dentro de otro o pasa de media línea, nadie la entenderá mañana. Extrae un
defcon nombre y docstring. - Creer que
sorted()modifica la lista: devuelve una lista nueva. (Las listas tienen también un método.sort()que sí ordena en el sitio; lo veremos en el módulo 4.) - Consejo: escribe la lambda pensando en la frase "para cada elemento, compárame por ___". Lo que rellene el hueco es el cuerpo de tu lambda.
Ejercicios
Ejercicio 1: estantería ordenada por stock
Con libros = list(zip(catalogo, precios, stocks)) (incluye "Fausto" con 21.00 € y stock 0: constrúyelo con catalogo + ["Fausto"], etc.), imprime los libros ordenados de menos a más stock, una línea por libro con el formato <título>: N uds.. Después, usa max() con key para imprimir cuál es el libro con más existencias.
Ejercicio 2: map/filter contra comprensiones
Partiendo de precios = [12.50, 9.95, 15.90, 21.00]: (a) obtén con map() y una lambda la lista de precios de socio (usa precio_final(base, socio=True)); (b) obtén con filter() y una lambda los precios base superiores a 12 €; (c) reescribe ambas como comprensiones; (d) escribe en una sola comprensión "precios de socio de los libros con base superior a 12 €" y explica por qué con map+filter sería menos legible.
Ejercicio 3: ordenar títulos por longitud
Ana quiere las etiquetas de la estantería ordenadas por longitud del título (más cortos primero) y, a igual longitud, alfabéticamente. Pista: la lambda puede devolver una tupla (criterio1, criterio2) — Python compara tuplas elemento a elemento, como viste al comparar cadenas. Aplícalo a ["El Quijote", "Hamlet", "Fausto", "La Odisea"].
Soluciones
Ejercicio 1:
titulos = catalogo + ["Fausto"]
bases = precios + [21.00]
unidades = stocks + [0]
libros = list(zip(titulos, bases, unidades))
for titulo, base, stock in sorted(libros, key=lambda libro: libro[2]):
print(f"{titulo}: {stock} uds.")
# Fausto: 0 uds. / La Odisea: 4 uds. / Hamlet: 6 uds. / El Quijote: 8 uds.
top = max(libros, key=lambda libro: libro[2])
print(f"Más existencias: {top[0]} ({top[2]} uds.)") # El Quijote (8 uds.)El stock es el elemento [2] de cada tupla; cambiar el criterio de orden es cambiar un índice en la lambda.
Ejercicio 2:
# (a) y (b)
socios = list(map(lambda base: precio_final(base, socio=True), precios))
# [12.35, 9.83, 15.71, 20.75]
caros = list(filter(lambda base: base > 12, precios))
# [12.5, 15.9, 21.0]
# (c) comprensiones equivalentes
socios = [precio_final(base, socio=True) for base in precios]
caros = [base for base in precios if base > 12]
# (d) transformar y cribar a la vez
socios_caros = [precio_final(base, socio=True) for base in precios if base > 12]
# [12.35, 15.71, 20.75]Con map+filter la versión (d) sería list(map(lambda b: precio_final(b, socio=True), filter(lambda b: b > 12, precios))): dos lambdas, dos llamadas anidadas y se lee de dentro hacia fuera. La comprensión expresa lo mismo de izquierda a derecha, como una frase.
Ejercicio 3:
titulos = ["El Quijote", "Hamlet", "Fausto", "La Odisea"]
ordenados = sorted(titulos, key=lambda t: (len(t), t))
print(ordenados) # ['Fausto', 'Hamlet', 'La Odisea', 'El Quijote']La lambda devuelve (longitud, título): Python ordena primero por longitud y usa el título como desempate. "Fausto" y "Hamlet" empatan a 6 letras y el orden alfabético los resuelve; "La Odisea" (9 caracteres, el espacio cuenta) precede a "El Quijote" (10). Sin el segundo elemento de la tupla, el orden entre los empatados a 6 letras dependería de su posición original.
Conclusión
Las lambdas completan tu caja de herramientas de funciones: sintaxis lambda params: expresion, cuerpo de una sola expresión con return implícito, y un territorio bien delimitado — criterios de usar y tirar para sorted(), min(), max(), map() y filter(), nunca sustitutas de def para lógica con nombre (PEP 8 dixit). De paso, el catálogo de Papyrus ya se ordena por precio, por stock o por lo que diga una lambda de una línea, y sabes cuándo una comprensión expresa lo mismo con más claridad. Pero hay un problema creciente: precio_final() está copiada en tarifa.py, y buscar_libro() vive encerrada en menu.py. Escribimos funciones para reutilizar... y seguimos copiándolas de archivo en archivo. La próxima lección resuelve exactamente eso: módulos y paquetes — crearemos papyrus_utils.py, el primer módulo propio de la tienda, y cualquier script podrá importar sus funciones con una línea.
Curso de Programación en Python
Módulo 1: Introducción a Python
- Introducción a Python
- Configuración del Entorno de Desarrollo
- Sintaxis de Python y Tipos de Datos Básicos
- Variables y Constantes
- Entrada y Salida Básica
- Entornos Virtuales y Gestión de Paquetes
Módulo 2: Estructuras de Control
- Sentencias Condicionales
- Bucles: for y while
- Herramientas de Control de Flujo
- Comprensiones de Listas
Módulo 3: Funciones y Módulos
- Definición de Funciones
- Argumentos de Función
- Funciones Lambda
- Módulos y Paquetes
- Visión General de la Biblioteca Estándar
Módulo 4: Estructuras de Datos
Módulo 5: Programación Orientada a Objetos
Módulo 6: Manejo de Archivos
- Lectura y Escritura de Archivos
- Trabajo con Archivos CSV
- Manejo de Datos JSON
- Operaciones con Archivos y Directorios
Módulo 7: Manejo de Errores y Excepciones
- Introducción a las Excepciones
- Manejo de Excepciones
- Lanzamiento de Excepciones
- Excepciones Personalizadas
- Buenas Prácticas y Registro de Errores con logging
Módulo 8: Temas Avanzados
- Anotaciones de Tipos (type hints)
- Decoradores
- Generadores
- Administradores de Contexto
- Concurrencia: Hilos y Procesos
- Asyncio para Programación Asíncrona
Módulo 9: Pruebas y Depuración
- Introducción a las Pruebas
- Pruebas Unitarias con unittest
- Pruebas con pytest
- Desarrollo Guiado por Pruebas
- Técnicas de Depuración
- Uso de pdb para Depuración
Módulo 10: Desarrollo Web con Python
- Introducción al Desarrollo Web
- Fundamentos del Framework Flask
- Construcción de APIs REST con Flask
- Introducción a Django
- Construcción de Aplicaciones Web con Django
Módulo 11: Ciencia de Datos con Python
- Introducción a la Ciencia de Datos
- NumPy para Computación Numérica
- Pandas para Manipulación de Datos
- Matplotlib para Visualización de Datos
- Introducción al Aprendizaje Automático con scikit-learn
