L'Anna vol veure el catàleg de Papyrus ordenat per preu, saber quin és el llibre més barat i filtrar els que tenen estoc. Totes aquestes operacions tenen una cosa en comú: una funció principal (sorted(), min(), un filtre) necessita que li diguis amb quin criteri treballar — i aquest criteri sol ser una funció diminuta, d'una sola expressió, que no mereix un def amb nom, docstring i lloc propi al fitxer. Per a aquests casos Python ofereix les funcions lambda: funcions anònimes d'una línia que s'escriuen just on es fan servir. En aquesta lliçó n'aprendràs la sintaxi, quan són una bona idea i quan no (PEP 8 té opinió ferma), i les posaràs a treballar amb sorted(), min()/max(), map() i filter() — comparant aquestes dues últimes amb les comprensions del mòdul 2, que sovint les superen.
Contingut
- Sintaxi: anatomia d'una lambda
- Lambda davant de
def: quan sí i quan no - El paràmetre
key: ordenar ambsorted() min()imax()amb criterimap()ifilter()davant de comprensions- Projecte: el catàleg de Papyrus ordenat per preu
Sintaxi: anatomia d'una lambda
Una lambda és una funció sense nom reduïda a la mínima expressió:
Comparem-la amb el seu def equivalent:
def preu_amb_iva(base):
return round(base * 1.04, 2)
# La mateixa logica com a lambda:
lambda base: round(base * 1.04, 2)Correspondència peça a peça:
def |
lambda |
|---|---|
def preu_amb_iva(base): |
lambda base: |
| Nom de la funció | No en té (és anònima) |
Cos de diverses línies, amb if, bucles, etc. |
Una única expressió |
return explícit |
Implícit: l'expressió ÉS el valor retornat |
| Docstring | No n'admet |
Restricció clau: el cos ha de ser una expressió — una cosa que produeix un valor. Hi caben operacions, crides a funcions, f-strings i el ternari del mòdul 2 ("esgotat" if estoc == 0 else "disponible"), però no sentències: res d'assignacions, while, for com a bucle ni diverses línies. Si necessites això, necessites un def.
Una lambda pot tenir diversos paràmetres (lambda titol, preu: f"{titol}: {preu} €") i fins i tot valors per defecte, tot i que a la pràctica la immensa majoria en reben un o dos.
Lambda davant de def: quan sí i quan no
Pots executar una lambda directament, i fins i tot guardar-la en una variable:
print((lambda base: round(base * 1.04, 2))(12.50)) # 13.0 — legal, pero rebuscat
amb_iva = lambda base: round(base * 1.04, 2) # legal, pero PEP 8 diu NOAquesta segona línia funciona... i PEP 8 la desaconsella explícitament: si li has de posar nom, fes servir def. Els motius són pràctics:
- Amb
def amb_iva(base): ..., els errors i les eines de depuració mostren el nomamb_iva; una lambda assignada apareix com l'anònim<lambda>, més difícil de rastrejar. defadmet docstring, diverses línies i creix sense reescriptures.- No s'hi guanya res:
amb_iva = lambda ...ocupa el mateix que eldefi comunica pitjor.
Llavors, per a què serveixen? Per passar-les com a argument a una altra funció, escrites al punt exacte on es fan servir. Aquesta és casa seva:
| Situació | Eina |
|---|---|
Criteri d'una línia per a sorted/min/max/map/filter, usat una sola vegada |
lambda |
| La mateixa lògica es fa servir en dos o més llocs | def amb nom |
El criteri necessita if/else de debò, validació o diverses línies |
def |
| Vols documentar què fa | def (docstring) |
| Assignar-la a un nom "per abreujar" | Mai: def |
Que una funció es pugui passar com a argument no t'hauria de sorprendre a aquestes altures: en Python les funcions són valors, com els nombres o les cadenes. cercar_llibre (sense parèntesis, lliçó 03-01) és un valor que anomena una funció; una lambda és el mateix, però sense passar per un nom.
El paràmetre key: ordenar amb sorted()
sorted(sequencia) retorna una llista nova ordenada (l'original no canvia):
print(sorted(preus)) # [9.95, 12.5, 15.9]
print(sorted(cataleg)) # ['El Quixot', 'Hamlet', "L'Odissea"] (ordre alfabetic)Però l'Anna no vol ordenar preus solts ni títols: vol ordenar llibres per preu. Primer aparellem títol i preu amb zip() (lliçó anterior) i llavors entra l'argument keyword key: una funció que, aplicada a cada element, produeix el valor pel qual ordenar.
llibres = list(zip(cataleg, preus))
# [("L'Odissea", 12.5), ('Hamlet', 9.95), ('El Quixot', 15.9)]
per_preu = sorted(llibres, key=lambda llibre: llibre[1])
# [('Hamlet', 9.95), ("L'Odissea", 12.5), ('El Quixot', 15.9)]Com funciona per dins: per a cada tupla llibre, sorted() crida la lambda, obté llibre[1] (el preu) i ordena les tuples comparant aquests valors. La lambda no ordena res; només respon a la pregunta "per quina de les teves dades et comparo?".
Variants útils:
mes_cars = sorted(llibres, key=lambda llibre: llibre[1], reverse=True) # descendent
alfabetic = sorted(cataleg, key=lambda t: t.lower()) # ignora majusculesreverse=True inverteix l'ordre — un altre argument keyword, com els de la lliçó 03-02. I key=lambda t: t.lower() resol el clàssic problema que les majúscules alterin l'ordre alfabètic.
min() i max() amb criteri
min() i max() accepten el mateix key, i responen preguntes de negoci en una línia:
mes_barat = min(llibres, key=lambda llibre: llibre[1])
mes_car = max(llibres, key=lambda llibre: llibre[1])
print(f"Ganga del dia: {mes_barat[0]} a {mes_barat[1]:.2f} €")
# Ganga del dia: Hamlet a 9.95 €
print(f"Joia de la botiga: {mes_car[0]} a {mes_car[1]:.2f} €")
# Joia de la botiga: El Quixot a 15.90 €Detall important: retornen l'element complet (la tupla títol-preu), no el valor del criteri. Per això mes_barat[0] dona el títol. Sense key, min(preus) donaria 9.95 — el preu, però sense saber de quin llibre.
map() i filter() davant de comprensions
Dues funcions clàssiques que reben una altra funció com a argument:
map(funcio, sequencia): aplica la funció a cada element (transforma).filter(funcio, sequencia): conserva els elements per als quals la funció retorna cert (garbella).
Totes dues són peresoses, com zip(): lliuren resultats a mesura que es demanen, i list() els materialitza.
# Transformar: preus de soci de tot el cataleg
list(map(lambda base: preu_final(base, soci=True), preus))
# [12.35, 9.83, 15.71]
# Garbellar: preus per sota de 13 €
list(filter(lambda base: base < 13, preus))
# [12.5, 9.95]Et sonen, aquestes operacions? Són exactament el que feies al mòdul 2 amb comprensions:
[preu_final(base, soci=True) for base in preus] # map
[base for base in preus if base < 13] # filterComparativa honesta:
map()/filter() + lambda |
Comprensió | |
|---|---|---|
| Llegibilitat amb lògica pròpia | Regular: la lambda afegeix soroll | Alta: es llegeix com una frase |
| Amb una funció ja existent | Molt bona: map(str.strip, linies) |
Bona: [l.strip() for l in linies] |
| Transformar i garbellar alhora | Maldestre: cal imbricar map(f, filter(g, x)) |
Natural: [f(x) for x in xs if g(x)] |
| Resultat | Peresós (cal fer list()) |
Llista directament |
| Estil dominant en el Python modern | En retrocés | Recomanat |
Criteri per al curs: fes servir comprensions per defecte; recorre a map()/filter() quan la funció ja existeixi i tingui nom (allà ni tan sols cal lambda). Tot i així, has de saber llegir-les: te les trobaràs constantment en codi d'altri.
Projecte: el catàleg de Papyrus ordenat per preu
Ajuntem-ho tot a tarifa.py: la taula del catàleg ordenada per preu, amb els preus de soci d'en Lluís i un petit informe:
IVA_LLIBRES = 0.04
DESCOMPTE_SOCI = 0.05
cataleg = ["L'Odissea", "Hamlet", "El Quixot"]
preus = [12.50, 9.95, 15.90]
estocs = [4, 6, 8]
def preu_final(base, soci=False, iva=IVA_LLIBRES):
"""Preu final: descompte de soci (si escau) i despres l'IVA."""
if soci:
base = base * (1 - DESCOMPTE_SOCI)
return round(base * (1 + iva), 2)
llibres = list(zip(cataleg, preus, estocs))
per_preu = sorted(llibres, key=lambda llibre: llibre[1])
print(f"{'Titol':<12} {'Base':>7} {'Soci':>7}")
print("-" * 28)
for titol, base, estoc in per_preu:
print(f"{titol:<12} {base:>5.2f} € {preu_final(base, soci=True):>5.2f} €")
mes_barat = min(llibres, key=lambda llibre: llibre[1])
print(f"\nRecomanacio economica: {mes_barat[0]} ({mes_barat[1]:.2f} €)")Titol Base Soci ---------------------------- Hamlet 9.95 € 9.83 € L'Odissea 12.50 € 12.35 € El Quixot 15.90 € 15.71 € Recomanacio economica: Hamlet (9.95 €)
Repassa el repartiment de papers: zip() empaqueta les llistes paral·leles en tuples per llibre, la lambda de key assenyala el preu com a criteri, sorted() ordena sense tocar les dades originals i preu_final() — la nostra funció amb nom, perquè es reutilitza — calcula la columna de socis. Cada eina al seu lloc.
Errors Comuns i Consells
- Assignar lambdes a noms (
f = lambda x: ...): funciona, però PEP 8 ho veta i les traces d'error mostraran<lambda>en comptes d'un nom útil. Fes servirdef. - Intentar posar sentències en una lambda (
lambda x: y = x + 1o diverses línies):SyntaxError. Una lambda és una expressió; si no hi cap, és undef. - Passar la lambda cridada en comptes de la lambda:
sorted(llibres, key=llibre[1])falla —keyespera una funció, no un valor. La forma correcta éskey=lambda llibre: llibre[1]. - Oblidar
list()ambmap()/filter():print(map(...))mostra<map object at 0x...>, no les dades. Materialitza-ho amblist()o recorre-ho ambfor. - Lambdes quilomètriques: si la teva lambda té un ternari dins d'un altre o passa de mitja línia, ningú no l'entendrà demà. Extreu un
defamb nom i docstring. - Creure que
sorted()modifica la llista: retorna una llista nova. (Les llistes tenen també un mètode.sort()que sí que ordena in situ; el veurem al mòdul 4.) - Consell: escriu la lambda pensant en la frase "per a cada element, compara'm per ___". El que ompli el buit és el cos de la teva lambda.
Exercicis
Exercici 1: prestatgeria ordenada per estoc
Amb llibres = list(zip(cataleg, preus, estocs)) (inclou-hi "Faust" amb 21.00 € i estoc 0: construeix-ho amb cataleg + ["Faust"], etc.), imprimeix els llibres ordenats de menys a més estoc, una línia per llibre amb el format <titol>: N unitats. Després, fes servir max() amb key per imprimir quin és el llibre amb més existències.
Exercici 2: map/filter contra comprensions
Partint de preus = [12.50, 9.95, 15.90, 21.00]: (a) obtén amb map() i una lambda la llista de preus de soci (fes servir preu_final(base, soci=True)); (b) obtén amb filter() i una lambda els preus base superiors a 12 €; (c) reescriu totes dues com a comprensions; (d) escriu en una sola comprensió "preus de soci dels llibres amb base superior a 12 €" i explica per què amb map+filter seria menys llegible.
Exercici 3: ordenar títols per longitud
L'Anna vol les etiquetes de la prestatgeria ordenades per longitud del títol (els més curts primer) i, a igual longitud, alfabèticament. Pista: la lambda pot retornar una tupla (criteri1, criteri2) — Python compara tuples element a element, com vas veure en comparar cadenes. Aplica-ho a ["El Quixot", "Hamlet", "Faust", "L'Odissea"].
Solucions
Exercici 1:
titols = cataleg + ["Faust"]
bases = preus + [21.00]
unitats = estocs + [0]
llibres = list(zip(titols, bases, unitats))
for titol, base, estoc in sorted(llibres, key=lambda llibre: llibre[2]):
print(f"{titol}: {estoc} unitats")
# Faust: 0 unitats / L'Odissea: 4 unitats / Hamlet: 6 unitats / El Quixot: 8 unitats
top = max(llibres, key=lambda llibre: llibre[2])
print(f"Mes existencies: {top[0]} ({top[2]} unitats)") # El Quixot (8 unitats)L'estoc és l'element [2] de cada tupla; canviar el criteri d'ordre és canviar un índex a la lambda.
Exercici 2:
# (a) i (b)
socis = list(map(lambda base: preu_final(base, soci=True), preus))
# [12.35, 9.83, 15.71, 20.75]
cars = list(filter(lambda base: base > 12, preus))
# [12.5, 15.9, 21.0]
# (c) comprensions equivalents
socis = [preu_final(base, soci=True) for base in preus]
cars = [base for base in preus if base > 12]
# (d) transformar i garbellar alhora
socis_cars = [preu_final(base, soci=True) for base in preus if base > 12]
# [12.35, 15.71, 20.75]Amb map+filter la versió (d) seria list(map(lambda b: preu_final(b, soci=True), filter(lambda b: b > 12, preus))): dues lambdes, dues crides imbricades i es llegeix de dins cap a fora. La comprensió expressa el mateix d'esquerra a dreta, com una frase.
Exercici 3:
titols = ["El Quixot", "Hamlet", "Faust", "L'Odissea"]
ordenats = sorted(titols, key=lambda t: (len(t), t))
print(ordenats) # ['Faust', 'Hamlet', 'El Quixot', "L'Odissea"]La lambda retorna (longitud, títol): Python ordena primer per longitud i fa servir el títol com a desempat. "Faust" (5 lletres) i "Hamlet" (6) van al davant per longitud; "El Quixot" i "L'Odissea" empaten a 9 caràcters (l'espai i l'apòstrof compten) i l'ordre alfabètic els resol. Sense el segon element de la tupla, l'ordre entre els empatats a 9 caràcters dependria de la seva posició original.
Conclusió
Les lambdes completen la teva caixa d'eines de funcions: sintaxi lambda params: expressio, cos d'una sola expressió amb return implícit, i un territori ben delimitat — criteris d'usar i llençar per a sorted(), min(), max(), map() i filter(), mai substitutes de def per a lògica amb nom (PEP 8 dixit). De passada, el catàleg de Papyrus ja s'ordena per preu, per estoc o pel que digui una lambda d'una línia, i saps quan una comprensió expressa el mateix amb més claredat. Però hi ha un problema creixent: preu_final() està copiada a tarifa.py, i cercar_llibre() viu tancada a menu.py. Escrivim funcions per reutilitzar... i continuem copiant-les de fitxer en fitxer. La propera lliçó resol exactament això: mòduls i paquets — crearem papyrus_utils.py, el primer mòdul propi de la botiga, i qualsevol script podrà importar-ne les funcions amb una línia.
Curs de Programació en Python
Mòdul 1: Introducció a Python
- Introducció a Python
- Configuració de l'Entorn de Desenvolupament
- Sintaxi de Python i Tipus de Dades Bàsics
- Variables i Constants
- Entrada i Sortida Bàsica
- Entorns Virtuals i Gestió de Paquets
Mòdul 2: Estructures de Control
Mòdul 3: Funcions i Mòduls
- Definició de Funcions
- Arguments de Funció
- Funcions Lambda
- Mòduls i Paquets
- Visió General de la Biblioteca Estàndard
Mòdul 4: Estructures de Dades
Mòdul 5: Programació Orientada a Objectes
Mòdul 6: Gestió de Fitxers
- Lectura i Escriptura de Fitxers
- Treball amb Fitxers CSV
- Gestió de Dades JSON
- Operacions amb Fitxers i Directoris
Mòdul 7: Gestió d'Errors i Excepcions
- Introducció a les Excepcions
- Gestió d'Excepcions
- Llançament d'Excepcions
- Excepcions Personalitzades
- Bones Pràctiques i Registre d'Errors amb logging
Mòdul 8: Temes Avançats
- Anotacions de Tipus (type hints)
- Decoradors
- Generadors
- Gestors de Context
- Concurrència: Fils i Processos
- Asyncio per a la Programació Asíncrona
Mòdul 9: Proves i Depuració
- Introducció a les Proves
- Proves Unitàries amb unittest
- Proves amb pytest
- Desenvolupament Guiat per Proves
- Tècniques de Depuració
- Ús de pdb per a la Depuració
Mòdul 10: Desenvolupament Web amb Python
- Introducció al Desenvolupament Web
- Fonaments del Framework Flask
- Construcció d'APIs REST amb Flask
- Introducció a Django
- Construcció d'Aplicacions Web amb Django
Mòdul 11: Ciència de Dades amb Python
- Introducció a la Ciència de Dades
- NumPy per a la Computació Numèrica
- Pandas per a la Manipulació de Dades
- Matplotlib per a la Visualització de Dades
- Introducció a l'Aprenentatge Automàtic amb scikit-learn
