A la lliçó 04-01 va aparèixer un detall que vam deixar aparcat: escriure pausar sense parèntesis no dona error. Python respon amb una cosa com ara <function pausar at 0x7f3c9a1b2e50>, i type(pausar) retorna <class 'function'>. Aleshores ho vam tractar com un parany que calia evitar; ara li donarem la volta i el convertirem en una eina.

Perquè aquell detall significa una cosa important: en Python, una funció és un valor com qualsevol altre. Si és un valor, es pot desar en una variable, imprimir, passar com a argument a una altra funció i retornar des d'una funció. I això obre una manera d'escriure codi diferent: en comptes de decidir dins d'una funció què farà exactament, se li passa el comportament des de fora.

És l'última peça del mòdul, i la que permet rematar TascaFàcil: les tres funcions demanar_text, demanar_opcio i demanar_enter comparteixen el mateix esquelet —pregunta, comprova, insisteix— i només es diferencien en quina comprovació fan. Si la comprovació pot ser un argument, les tres es converteixen en una.

Contingut

  1. Una funció sense parèntesis és un valor
  2. Funcions d'ordre superior
  3. Funcions anònimes amb lambda
  4. lambda o def: quan cadascun
  5. Retornar una funció: fàbriques
  6. El patró callback a TascaFàcil
  7. Ordre superior sobre col·leccions: una avançada
  8. Errors comuns i consells
  9. Exercicis
  10. Conclusió del mòdul

  1. Una funció sense parèntesis és un valor

Comencem per comprovar-ho al REPL, sense trucs.

def saludar():
    print("Bon dia, equip d'Estudi Alba.")

print(saludar)          # <function saludar at 0x7f3c9a1b2e50>
print(type(saludar))    # <class 'function'>
print(saludar())        # executa la funcio i despres imprimeix None

Les dues primeres línies tracten saludar com a dada: l'imprimeixen i en consulten el tipus, igual que faries amb un número. La tercera el tracta com a acció: els parèntesis són l'operador de crida.

Si és un valor, es pot assignar a una altra variable:

def mostrar_capcalera():
    print("TascaFacil - Estudi Alba")

pintar = mostrar_capcalera    # sense parentesis: copiem la referencia
pintar()                      # TascaFacil - Estudi Alba
print(pintar is mostrar_capcalera)  # True

Fixa't bé en el que ha passat: no s'ha copiat la funció, sinó que ara hi ha dos noms apuntant al mateix objecte funció, tal com vas veure amb les referències a 04-03. L'is ho confirma. A partir d'aquell moment, pintar() i mostrar_capcalera() són exactament la mateixa crida.

Expressió Què és Què val
mostrar_capcalera L'objecte funció <function mostrar_capcalera at 0x…>
mostrar_capcalera() Una crida El que retorni (aquí, None)
type(mostrar_capcalera) El tipus de l'objecte <class 'function'>

Això val també per a les funcions integrades i per als mètodes: str.upper, len o input sense parèntesis són valors que pots desar i passar per aquí. És la base de tot el que ve.

  1. Funcions d'ordre superior

Una funció d'ordre superior és aquella que rep una altra funció com a paràmetre o retorna una funció. Sona abstracte; l'exemple ho aclareix en tres línies.

def aplicar_format(text, transformacio):
    """Aplica al text la transformacio rebuda i retorna el resultat."""
    return transformacio(text)

print(aplicar_format("cartell fira del llibre", str.upper))
print(aplicar_format("CARTELL FIRA DEL LLIBRE", str.lower))
print(aplicar_format("cartell fira del llibre", str.title))
CARTELL FIRA DEL LLIBRE
cartell fira del llibre
Cartell Fira Del Llibre

Dins d'aplicar_format, el paràmetre transformacio és un nom local que apunta a la funció que li hagin passat; en escriure transformacio(text) es crida allò que hagi arribat. La funció no sap ni li importa què és: el comportament ve de fora.

I funciona igual amb funcions pròpies:

def marcar_urgent(text):
    """Retorna el text envoltat de marques d'urgencia."""
    return f">>> {text.upper()} <<<"

def amb_longitud(text):
    """Retorna el text seguit del seu nombre de caracters."""
    return f"{text} ({len(text)} caracters)"

print(aplicar_format("cartell fira del llibre", marcar_urgent))
print(aplicar_format("cartell fira del llibre", amb_longitud))
>>> CARTELL FIRA DEL LLIBRE <<<
cartell fira del llibre (23 caracters)

Observa el detall decisiu: a aplicar_format(text, marcar_urgent), marcar_urgent va sense parèntesis. Estem passant la funció, no el seu resultat. Si escrivissis marcar_urgent(...) l'estaries cridant allà mateix i passant la cadena que retorna, que és una altra cosa completament diferent —i la font número u d'errors en aquesta lliçó.

L'avantatge d'aquest patró és que separa el que es repeteix del que canvia. aplicar_format és un esquelet; la transformació concreta és un buit que s'omple a cada crida. Veuràs que és exactament el problema de les nostres tres funcions demanar_*.

  1. Funcions anònimes amb lambda

De vegades la funció que vols passar és tan petita que donar-li nom i tres línies de def resulta desproporcionat. Per a això hi ha lambda: una expressió que crea una funció sense nom.

lambda parametres: expressio

Les tres parts: la paraula lambda, els paràmetres separats per comes (sense parèntesis) i, després dels dos punts, una única expressió el valor de la qual es retorna automàticament. No s'escriu return: està implícit.

doble = lambda n: n * 2
print(doble(21))        # 42
print(type(doble))      # <class 'function'>

Aquesta lambda és equivalent, punt per punt, a def doble(n): return n * 2. El seu tipus és el mateix, function, i es crida igual. On de veritat brilla és dins d'una altra crida, sense donar-li nom:

print(aplicar_format("cartell fira", lambda t: t.strip().capitalize()))
print(aplicar_format("cartell fira", lambda t: f"[{t}]"))
print(aplicar_format("cartell fira", lambda t: t.replace(" ", "_")))
Cartell fira
[cartell fira]
cartell_fira

Tres comportaments diferents sense escriure tres def. La limitació, que és forta, ja l'has vista: una lambda només pot contenir una expressió. Res de if/elif en diverses línies, res de bucles, res d'assignacions, res de diverses sentències. Sí que hi cap l'operador ternari de 03-01, perquè és una expressió:

etiqueta = lambda dies: "llarga" if dies > 5 else "curta"
print(etiqueta(9))      # llarga
print(etiqueta(2))      # curta

I un advertiment d'estil que convé interioritzar ja: doble = lambda n: n * 2 és correcte però desaconsellat. Si li has de posar nom a la funció, fes servir def: et dona docstring, un nom visible als missatges d'error i lloc per créixer. La lambda està pensada per a funcions sense nom, fetes servir al mateix lloc.

  1. lambda o def: quan cadascun

Criteri lambda def
Nom Anònima Té nom propi
Cos Una sola expressió Tantes sentències com calgui
return Implícit Explícit
Docstring No n'admet
En un traceback apareix com <lambda> El nom real de la funció
Ús típic Argument d'una altra funció, d'usar i llençar Tota la resta
Reutilitzable des de diversos llocs Malament

La regla de decisió pràctica cap en tres preguntes. La faràs servir més d'una vegada? Doncs def. Necessita més d'una línia, o mereix una explicació? Doncs def. És una condició o una transformació trivial que es llegeix millor dins de la crida que com una definició a part, tres pantalles més amunt? Doncs lambda.

Aquest últim cas és real i no és rar: quan el criteri és evident, escriure'l al mateix lloc on es fa servir estalvia al lector un viatge a una altra banda del fitxer. Però tan bon punt la lambda comença a tenir parèntesis imbricats o un ternari dins d'un altre, ha deixat d'aclarir i toca ascendir-la a def.

  1. Retornar una funció: fàbriques

L'altra meitat de l'ordre superior és retornar una funció. Una funció que fabrica funcions s'anomena, informalment, una fàbrica, i es recolza en les funcions imbricades i l'àmbit envolupant que vas veure a 04-03.

def crear_salutacio(nom_estudi):
    """Retorna una funcio que saluda en nom de l'estudi indicat."""
    def saludar(persona):
        return f"Hola {persona}, benvinguda a {nom_estudi}."
    return saludar          # sense parentesis: retornem la funcio

saludar_alba = crear_salutacio("Estudi Alba")
saludar_sole = crear_salutacio("Forn Sole")

print(saludar_alba("Marta"))
print(saludar_alba("Luis"))
print(saludar_sole("Nuria"))
Hola Marta, benvinguda a Estudi Alba.
Hola Luis, benvinguda a Estudi Alba.
Hola Nuria, benvinguda a Forn Sole.

Analitza el que passa, perquè és més subtil del que sembla. crear_salutacio("Estudi Alba") s'executa, defineix la funció interna saludar i la retorna; en aquell moment crear_salutacio acaba, i segons el que vas aprendre a 04-03 la seva variable local nom_estudi hauria de desaparèixer. Però no desapareix: la funció retornada se'n porta amb ella el valor que tenia. Per això saludar_alba i saludar_sole són dues funcions diferents, cadascuna amb el seu estudi a dins.

Aquest mecanisme té nom —clausura, o closure— i no cal que el dominis avui. N'hi ha prou de quedar-te amb la idea: una fàbrica et permet crear versions especialitzades d'una funció a partir d'uns paràmetres de configuració, en comptes de repetir aquests paràmetres a totes les crides.

def crear_validador_de_rang(minim, maxim):
    """Retorna una funcio que comprova si un text es un enter del rang."""
    def es_valid(text):
        return text.isdigit() and minim <= int(text) <= maxim
    return es_valid

es_dia_valid = crear_validador_de_rang(1, 365)
es_hora_valida = crear_validador_de_rang(0, 23)

print(es_dia_valid("400"))      # False
print(es_dia_valid("4"))        # True
print(es_hora_valida("22"))     # True

Dos validadors complets a partir d'una sola definició. Guarda aquesta idea: la farem servir ara mateix.

  1. El patró callback a TascaFàcil

Mira les tres funcions de 04-02 traient-los la carn:

valor = input(missatge).strip()
while <la comprovacio que sigui>:
    valor = input(<l'avis que sigui>).strip()
return valor

Les tres tenen el mateix esquelet i difereixen només en la comprovació. Ja saps què cal fer: convertir aquesta comprovació en un paràmetre. A la funció que es passa perquè una altra la cridi se l'anomena callback —«funció de retorn de crida»—, perquè qui la rep la crida de tornada quan la necessita.

def demanar_dada(missatge, validador, avis="Valor no valid."):
    """Demana una dada i la retorna quan el validador la dona per bona."""
    valor = input(missatge).strip()
    while not validador(valor):
        print(avis)
        valor = input(missatge).strip()
    return valor

Vuit línies que substitueixen les tres funcions anteriors. El contracte és explícit: validador ha de ser una funció que rebi un text i retorni True o False. I ara els validadors, uns amb def i altres amb lambda, segons el que aconsella la taula de la secció 4:

PRIORITATS = ("alta", "mitjana", "baixa")
EQUIP = ("marta", "luis", "nuria")

def es_dies_valid(text):
    """Comprova que el text sigui un enter de dies entre 1 i 365."""
    return text.isdigit() and 1 <= int(text) <= 365

titol = demanar_dada("Titol         : ",
                     lambda t: t != "",
                     "El titol no pot estar buit.")
responsable = demanar_dada("Responsable   : ",
                           lambda t: t.lower() in EQUIP,
                           f"Ha de ser un de: {', '.join(EQUIP)}.")
prioritat = demanar_dada("Prioritat     : ",
                         lambda t: t.lower() in PRIORITATS,
                         "Prioritat: alta, mitjana o baixa.")
dies = int(demanar_dada("Dies (1-365)  : ", es_dies_valid,
                        "Ha de ser un enter entre 1 i 365."))

Quatre observacions sobre aquest codi:

  • Les tres primeres validacions són trivials i van en lambda, escrites just on es fan servir: es llegeix d'una ullada què es considera vàlid per a cada camp. La dels dies porta dues comprovacions encadenades i mereix un def amb nom i docstring.
  • es_dies_valid va sense parèntesis. Estem passant la funció, no cridant-la. És l'error que més es comet aquí.
  • L'avís és un paràmetre amb valor per defecte (04-02), així que cada camp pot explicar el seu propi criteri sense obligar a fer-ho sempre.
  • demanar_dada retorna sempre text, i la conversió a enter es fa fora, a la crida. És coherent amb 04-04: la funció d'entrada valida, i qui la fa servir decideix quin tipus vol.

Ha millorat el programa? Depèn de quins valors. La taula honesta:

Tres funcions demanar_* (04-02) Una demanar_dada amb callback
Funcions a mantenir 3 1
Flexibilitat Només els tres casos previstos Qualsevol criteri imaginable
Llegibilitat de la crida Molt alta (demanar_enter(m, 1, 365)) Mitjana: cal llegir la lambda
Dificultat per a un principiant Baixa Mitjana

No hi ha un guanyador absolut, i convé dir-ho clar: l'ordre superior és una eina, no una millora automàtica. Per a un programa petit amb tres tipus de dada, les tres funcions específiques es llegeixen millor. Quan els criteris de validació es multipliquen i canvien sovint, demanar_dada guanya de pallissa. L'important és que ara pots triar, i que reconeixes el patró quan el vegis en codi aliè, on és habitual.

  1. Ordre superior sobre col·leccions: una avançada

El lloc on l'ordre superior es fa servir diàriament és sobre col·leccions de dades: ordenar una llista de tasques per prioritat, quedar-se amb les de la Nuria, transformar-les totes de cop. Les eines clàssiques són sorted amb el seu paràmetre key, i map i filter.

Com que encara no coneixes les llistes, vegem-ho amb el que sí que tens: una cadena, que és una seqüència de caràcters. (sorted retorna una llista de caràcters, així que els tornem a unir amb "".join(...) per poder imprimir-los com a text; les llistes arriben al mòdul 5.)

print("".join(sorted("nuria")))                    # ainru
print("".join(sorted("Nuria")))                    # Nairu
print("".join(sorted("Nuria", key=str.lower)))     # aiNru
print(max("Marta", "Luis", key=len))               # Marta

Fixa't en key=str.lower: és un argument per nom el valor del qual és una funció. sorted la crida una vegada per cada caràcter i ordena segons el que retorni, no segons el caràcter original. Per això la tercera línia ignora majúscules i minúscules mentre que la segona posa la N al principi —les majúscules van abans en l'ordre dels caràcters. I max(..., key=len) tria la cadena més llarga en comptes de la major alfabèticament: el mateix max de sempre, amb un criteri diferent injectat des de fora.

Aquest és el patró sencer. sorted no sap ordenar tasques per prioritat, però sí que sap ordenar qualsevol cosa si li dius amb una funció què ha de mirar de cada element. Quan arribis a Llistes i arrays tindràs col·leccions de veritat sobre les quals aplicar-ho, i a Algorismes d'ordenació veuràs com funciona l'ordenació per dins. map i filter, que apliquen una funció a tots els elements o seleccionen els que la compleixen, s'estudien també amb col·leccions; de moment n'hi ha prou que en reconeguis la forma quan la trobis.

Errors Comuns i Consells

Passar la crida en comptes de la funció. aplicar_format(text, marcar_urgent()) executa marcar_urgent allà mateix —a més sense arguments, amb la qual cosa dona TypeError— i passa el seu resultat. Per passar la funció, escriu-la sense parèntesis.

Oblidar els parèntesis dins de la funció d'ordre superior. L'error simètric: si a dins escrius return transformacio en comptes de transformacio(text), retornes l'objecte funció. Símptoma: s'imprimeix <function … at 0x…> on esperaves un text.

Ficar massa coses en una lambda. Si necessites un if de diverses branques, un bucle o una assignació, no hi cap: SyntaxError. Fes servir def.

Posar nom a una lambda. doble = lambda n: n * 2 funciona, però perds docstring i un nom útil als errors. Si porta nom, que sigui def.

Confondre el paràmetre amb la funció que arriba. Dins de demanar_dada, validador és un nom local: pot rebre avui una lambda i demà un def. La funció només exigeix que sigui cridable amb un text i retorni un booleà.

Consell: documenta el contracte del callback. Al docstring, digues què ha de rebre i què ha de retornar la funció que et passen. Sense això, qui faci servir la teva funció s'haurà de llegir el cos.

Consell: no facis servir ordre superior perquè quedi sofisticat. Si dos if resolen el problema amb més claredat, dos if. L'abstracció es paga en esforç de lectura, i només compensa quan estalvia repetició real.

Exercicis

Exercici 1: Predir la sortida

Digues què imprimeix cada línia, i si escau quin error es produeix i per què.

def emmarcar(text):
    return f"<<{text}>>"

f = emmarcar
print(f("Forn Sole"))
print(f)
print(emmarcar is f)

def aplicar(valor, funcio):
    return funcio(valor)

print(aplicar("Forn Sole", emmarcar))
print(aplicar("Forn Sole", emmarcar("Vidal")))

Exercici 2: Fàbrica d'etiquetadors

Escriu crear_etiquetador(prefix, ample), una funció que retorni una altra funció. La funció retornada rep un text i retorna el prefix alineat a l'esquerra en 10 caràcters seguit del text alineat a la dreta en ample caràcters, fent servir el formatatge d'f-strings de 02-03. Crea amb ella dos etiquetadors —un per a tasques urgents amb prefix [URGENT] i ample 30, i un altre per a notes amb prefix [nota] i ample 24— i prova'ls amb «Cartell fira del llibre».

Exercici 3: Validadors per a TascaFàcil

Fent servir demanar_dada(missatge, validador, avis) de la secció 6, escriu les expressions de crida necessàries per demanar aquestes tres dades, decidint en cada cas si el validador ha de ser lambda o def i justificant-ho:

  1. Un codi de client: exactament quatre dígits.
  2. Una resposta de sí o no: s o n, sense distingir majúscules.
  3. Un percentatge d'avanç: enter entre 0 i 100, i a més múltiple de 5.

Solucions

Solució 1.

<<Forn Sole>>
<function emmarcar at 0x...>
True
<<Forn Sole>>
TypeError: 'str' object is not callable

f = emmarcar crea un segon nom per al mateix objecte, així que f("Forn Sole") emmarca igual i emmarcar is f és True. print(f) mostra la representació de l'objecte funció. La primera crida a aplicar passa la funció i funciona. L'última passa emmarcar("Vidal"), que s'avalua abans de la crida i val "<<Vidal>>": dins d'aplicar, funcio és una cadena, i en intentar funcio(valor) Python protesta amb 'str' object is not callable. És el mateix missatge que veies en ombrejar un built-in a 04-03, i pel mateix motiu: s'està cridant una cosa que no és una funció.

Solució 2.

def crear_etiquetador(prefix, ample):
    """Retorna una funcio que etiqueta textos amb el prefix i ample donats."""
    def etiquetar(text):
        return f"{prefix:<10}{text:>{ample}}"
    return etiquetar

etiquetar_urgent = crear_etiquetador("[URGENT]", 30)
etiquetar_nota = crear_etiquetador("[nota]", 24)

print(etiquetar_urgent("Cartell fira del llibre"))
print(etiquetar_nota("Cartell fira del llibre"))
[URGENT]         Cartell fira del llibre
[nota]     Cartell fira del llibre

La funció interna etiquetar llegeix prefix i ample de l'àmbit envolupant (la E de LEGB, 04-03) i continua recordant-los després que crear_etiquetador hagi acabat. Compara aquest enfocament amb passar prefix i ample a cada crida: la fàbrica t'estalvia repetir-los, i de passada deixa clar que un etiquetador d'urgències és una cosa i un de notes és una altra.

Solució 3.

codi = demanar_dada("Codi de client: ",
                    lambda t: t.isdigit() and len(t) == 4,
                    "Han de ser exactament quatre digits.")

resposta = demanar_dada("Confirmes? (s/n): ",
                        lambda t: t.lower() in ("s", "n"),
                        "Respon s o n.")

def es_avanc_valid(text):
    """Comprova que sigui un enter de 0 a 100 multiple de 5."""
    if not text.isdigit():
        return False
    valor = int(text)
    return 0 <= valor <= 100 and valor % 5 == 0

avanc = int(demanar_dada("Avanc (0-100): ", es_avanc_valid,
                         "Enter de 0 a 100, multiple de 5."))

Els dos primers criteris caben folgadament en una expressió i es llegeixen millor dins de la crida, així que lambda. El tercer necessita convertir el text a número després de comprovar que es pot convertir, i això són dos passos amb una variable intermèdia: impossible en una lambda llegible, i a més mereix un docstring que expliqui la regla del múltiple de 5. Aquell if not text.isdigit(): return False és, per cert, la clàusula de guarda de 04-02 fent la seva feina: sense ella, int(text) rebentaria amb un ValueError davant de qualsevol lletra.

Conclusió del mòdul

Les funcions també són dades. Sense parèntesis, un nom de funció és un valor de tipus function que es pot imprimir, assignar a una altra variable i passar per aquí; amb parèntesis, és una crida. D'aquí surten les funcions d'ordre superior: les que reben una funció com a paràmetre —l'esquelet el poses tu, el comportament concret el posa qui crida— i les que retornen una funció, les fàbriques, capaces de crear versions especialitzades que recorden la seva configuració. Per a les funcions diminutes d'usar i llençar hi ha lambda, limitada a una sola expressió i sense return explícit; per a tota la resta, def. I el patró callback —passar la comprovació com a argument— ha unificat les tres funcions demanar_* de TascaFàcil en una sola demanar_dada.

Amb això es tanca el mòdul 4. Saps definir i cridar funcions, i distingir la definició de la crida (04-01). Saps donar-los paràmetres posicionals, per nom i amb valors per defecte, i fer que retornin un o diversos valors amb return, entenent per què retornar gairebé sempre guanya a imprimir (04-02). Saps on viu cada nom gràcies a la regla LEGB, per què les globals canviants són un problema i per què les constants no ho són (04-03). Saps descompondre un programa sencer amb criteris comprovables: una responsabilitat per funció, nivells d'abstracció homogenis, entrada i sortida separades de la lògica, DRY sense sobrefragmentar, i un main() protegit per if __name__ == "__main__": (04-04). I saps que les funcions són valors que es passen i es retornen (04-05).

tascafacil.py ha arribat a la versió 0.7: constants a dalt, funcions agrupades per famílies, un main() de vint-i-dues línies que es llegeix com un índex, zero duplicació i vuit peces reutilitzables en qualsevol altre programa. És un salt enorme respecte al mur de la v0.6.

I tanmateix, continua gestionant una sola tasca. La Marta no pot tenir alhora el cartell de la fira del llibre, el menú del Forn Solé i l'encàrrec del client Vidal: registrar-ne una destrueix l'anterior. Les sis variables soltes —titol, responsable, prioritat, dies, completada, hi_ha_tasca— viatgen juntes de funció en funció perquè són, en realitat, una sola cosa sense nom. I mostrar_fitxa(titol, responsable, prioritat, dies, completada) amb els seus cinc paràmetres n'és el símptoma més visible.

Les dues coses —desar moltes tasques i agrupar les dades de cadascuna— es resolen amb el mateix: estructures de dades. És el mòdul 5, que comença amb Llistes i arrays: l'eina que permetrà per fi que TascaFàcil gestioni la feina real d'Estudi Alba, recórrer-la amb un for, cercar-hi, ordenar-la per prioritat i, quan arribem als fitxers, que continuï allà demà al matí.

© Copyright 2026. Tots els drets reservats