Les tres funcions que vam escriure a la lliçó anterior —mostrar_capcalera(), mostrar_menu() i pausar()— tenen una limitació evident: fan sempre exactament el mateix. mostrar_menu() pinta aquell menú i no cap altre; pausar() mostra aquell missatge i no cap altre. Són útils, però rígides. I per això mateix no ens han servit per al problema de fons de TascaFàcil: els quatre bucles de validació repetits, que s'assemblen entre si però no són idèntics —cadascun té el seu missatge, els seus valors acceptables i el seu tipus de dada.

Per extreure això calen les dues peces que falten. La primera, dir coses a la funció quan la crides: això són els paràmetres, i converteixen una funció que fa una cosa en una funció que fa tota una família de coses. La segona, que la funció et lliuri un resultat: això és return, i és el que permet fer servir el valor calculat a qualsevol lloc, desar-lo en una variable o passar-lo a una altra funció.

Amb paràmetres i return, una funció deixa de ser una drecera per escriure menys i es converteix en una peça de construcció: alguna cosa amb entrades i sortides ben definides que pots muntar amb altres peces. Aquesta és la lliçó més important del mòdul.

Contingut

  1. Paràmetres i arguments: dues paraules per a dues coses
  2. Arguments posicionals i arguments per nom
  3. Valors per defecte
  4. return: retornar un valor i acabar
  5. Diversos return i el return primerenc
  6. Retornar més d'un valor
  7. Imprimir a dins o retornar?
  8. *args i **kwargs, per reconèixer-los
  9. TascaFàcil: les funcions de validació
  10. Errors comuns i consells
  11. Exercicis
  12. Conclusió

  1. Paràmetres i arguments: dues paraules per a dues coses

Un paràmetre és el nom que apareix entre els parèntesis de la definició; un argument és el valor concret que es lliura entre els parèntesis de la crida.

def saludar(nom):               # 'nom' es el PARAMETRE
    print(f"Hola, {nom}. Benvinguda a Estudi Alba.")

saludar("Marta")                # "Marta" es l'ARGUMENT
saludar("Luis")
Hola, Marta. Benvinguda a Estudi Alba.
Hola, Luis. Benvinguda a Estudi Alba.

El que passa a la crida és exactament això: Python crea, dins de la funció, una variable anomenada nom i li assigna el valor "Marta" —una assignació normal i corrent, nom = "Marta", feta automàticament per tu. Aquesta variable viu només mentre la funció s'executa i desapareix en acabar; el perquè exacte és el tema d'Àmbit de variables.

Paràmetre Argument
On apareix A la definició (def) A la crida
Què és Un nom, un buit per omplir Un valor concret
Quants n'hi ha Fixos en escriure la funció Un per cada paràmetre, a cada crida
Exemple def saludar(nom): saludar("Nuria")

Distingir-los importa perquè els missatges d'error de Python parlen en aquests termes: cridar saludar() sense res produeix TypeError: saludar() missing 1 required positional argument: 'nom', és a dir, «falta 1 argument obligatori i es diu nom». Si en passes de més, dirà takes 1 positional argument but 2 were given. Tan bon punt entens el vocabulari, aquests errors deixen de fer por i passen a ser instruccions força precises.

Una funció pot tenir diversos paràmetres, separats per comes, i llavors l'ordre mana:

def presentar_tasca(titol, responsable, dies):
    print(f"{titol} -> {responsable} ({dies} dies)")

presentar_tasca("Cartell fira del llibre", "Nuria", 4)
presentar_tasca("Nuria", 4, "Cartell fira del llibre")   # valid, encara que absurd
Cartell fira del llibre -> Nuria (4 dies)
Nuria -> 4 (Cartell fira del llibre dies)

La segona crida no dona error: Python assigna per posició sense jutjar si té sentit. És una fallada silenciosa, de les pitjors, i la secció següent n'ofereix la defensa.

  1. Arguments posicionals i arguments per nom

Fins ara hem passat arguments posicionals: el primer valor va al primer paràmetre, el segon al segon, i així. Però Python permet també anomenar el paràmetre al qual va cada valor. S'anomenen arguments per nom o keyword arguments:

presentar_tasca(titol="Menu del Forn Sole", responsable="Luis", dies=3)
presentar_tasca(responsable="Luis", dies=3, titol="Menu del Forn Sole")

Les dues crides fan exactament el mateix: en indicar el nom, l'ordre deixa d'importar. I de passada, la crida es llegeix sola. Compara crear_avis("Cartell fira", True, False, 3) amb crear_avis("Cartell fira", urgent=True, silencios=False, dies=3): sense els noms, per entendre la primera hauries d'anar a buscar la definició de crear_avis; amb ells, la crida s'explica al lloc on és.

Es poden barrejar els dos estils, amb una regla ferma: els posicionals van sempre abans que els nomenats.

presentar_tasca("Cartell fira", responsable="Nuria", dies=4)   # correcte
presentar_tasca(titol="Cartell fira", "Nuria", 4)              # SyntaxError

Criteri pràctic: fes servir arguments per nom quan el valor sigui un True/False solt, un número sense unitats evidents o quan la funció tingui més de dos o tres paràmetres; un True despullat en una crida és una endevinalla per a qui llegeixi el codi d'aquí a un mes.

  1. Valors per defecte

Un paràmetre pot portar un valor per defecte, que es fa servir si qui crida no proporciona aquell argument. S'escriu amb un = a la definició:

def avisar(missatge, vegades=1):
    """Repeteix un avis per pantalla el nombre de vegades indicat."""
    for i in range(vegades):
        print(f"[AVIS] {missatge}")

avisar("Revisar l'encarrec del client Vidal")   # imprimeix 1 linia
avisar("Lliurament dema", vegades=3)            # imprimeix 3 linies

vegades s'ha tornat opcional: la primera crida fa servir el valor per defecte, 1, i la segona el sobreescriu. Així s'aconsegueix que una funció sigui senzilla en el cas normal i flexible en l'especial, que és exactament el que vols. Hi ha una regla sintàctica que Python imposa: els paràmetres amb valor per defecte van al final, després de tots els obligatoris. És a dir, def demanar_text(missatge, obligatori=True): és correcte, però def demanar_text(obligatori=True, missatge): produeix SyntaxError: parameter without a default follows parameter with a default. La raó és de sentit comú: si els opcionals poguessin anar al mig, Python no sabria a quin paràmetre correspon el segon valor d'una crida posicional.

Un avís que convé gravar des d'avui, encara que la seva explicació completa arribi més endavant: no facis servir com a valor per defecte un objecte mutable (una llista, per exemple). El valor per defecte s'avalua una sola vegada, en definir la funció, no a cada crida; si és mutable i es modifica a dins, aquesta modificació persisteix entre crides i produeix un comportament desconcertant. Amb valors immutables —números, cadenes, booleans, None— no hi ha cap problema, i és l'únic que farem servir de moment. Hi tornarem quan coneguis les llistes.

  1. return: retornar un valor i acabar

return fa dues coses alhora, i totes dues importen:

  1. Lliura un valor a qui ha cridat la funció.
  2. Acaba la funció immediatament: cap línia posterior del cos no s'executa.
def calcular_cost(dies, tarifa_diaria):
    """Retorna el cost total d'una tasca, sense imprimir res."""
    return dies * tarifa_diaria

cost = calcular_cost(4, 120.0)
print(f"Cost estimat: {cost:.2f} EUR")
print(f"Amb IVA: {cost * 1.21:.2f} EUR")
Cost estimat: 480.00 EUR
Amb IVA: 580.80 EUR

Fixa't en el que ha guanyat el programa. calcular_cost no sap ni li importa què es farà amb el número: el lliura i s'aparta. Qui el crida el desa a cost, el formata i el reutilitza per a l'IVA. Una crida a una funció que retorna valor és una expressió, amb tot el que això significa: pot anar dins d'un print, d'un if, d'una operació aritmètica o dins d'una altra crida.

print(calcular_cost(2, 90.0))                              # dins de print
if calcular_cost(10, 120.0) > 1000:                        # dins d'una condicio
    print("Pressupost alt: requereix aprovacio de la Marta.")
total = calcular_cost(3, 100.0) + calcular_cost(2, 80.0)   # en una suma

I la segona meitat de return, la d'acabar, es veu així: si escrius return 42 i a sota un print("..."), aquest print és codi mort i no s'executa mai; els editors solen marcar-lo en gris. Si te'n surt un, o sobra o el return és al lloc equivocat.

  1. Diversos return i el return primerenc

Una funció pot tenir tants return com calgui. S'executa el primer que s'assoleixi, i allà s'acaba tot:

def etiqueta_de_carrega(dies):
    """Retorna una etiqueta de carrega de treball segons els dies estimats."""
    if dies <= 2:
        return "lleugera"
    if dies <= 5:
        return "mitjana"
    return "pesada"

print(etiqueta_de_carrega(1))    # lleugera
print(etiqueta_de_carrega(4))    # mitjana
print(etiqueta_de_carrega(9))    # pesada

Fixa't que no hi ha ni elif ni else, i tanmateix funciona: com que cada branca acaba en return, si la primera condició es compleix la funció ja ha acabat i mai no arriba a la segona. Aquesta és la clàusula de guarda de 03-01 portada a les funcions: comprovar primer els casos particulars, sortir tan bon punt es resolen i deixar el cas general al final, sense indentar. Es llegeix molt millor que un if imbricat de tres nivells.

Un return sense valor —només la paraula— també existeix: acaba la funció i retorna None. És útil per abandonar aviat un procediment:

def mostrar_fitxa(hi_ha_tasca, titol):
    if not hi_ha_tasca:
        print("Encara no hi ha cap tasca registrada.")
        return                       # sortim: no hi ha res a pintar
    print(f"Fitxa de: {titol}")

Compte amb un descuit habitual: si una branca oblida el seu return, aquesta branca retorna None en silenci, i la funció sembla correcta fins que algú la crida amb el valor que hi cau just. Comprova una per una que totes les branques retornen alguna cosa.

  1. Retornar més d'un valor

Si separes diversos valors amb comes després de return, la funció els retorna tots alhora:

def analitzar_titol(text):
    """Retorna el titol normalitzat i el seu nombre de caracters."""
    net = text.strip().capitalize()
    return net, len(net)

titol, longitud = analitzar_titol("   cartell fira del llibre  ")
print(titol)       # Cartell fira del llibre
print(longitud)    # 23

El que passa per dins és que Python empaqueta els dos valors en un únic objecte anomenat tupla, i la línia titol, longitud = ... el desempaqueta en dues variables. Pots comprovar-ho desant el resultat en una sola variable: resultat = analitzar_titol("cartell fira") deixa a resultat el valor ('Cartell fira', 12), el type() del qual és <class 'tuple'>. Les tuples són el tema de Tuples i estructures imbricades; avui n'hi ha prou de saber que el desempaquetatge funciona i que el nombre de variables de l'esquerra ha de coincidir amb el de valors retornats, o tindràs un ValueError: too many values to unpack.

Un consell de disseny: retornar dos valors està bé; retornar-ne cinc és senyal que aquella funció fa massa coses, o que aquests cinc valors són en realitat un sol concepte que encara no té nom —just el que li passa a TascaFàcil amb titol, responsable, prioritat, dies i completada, i que es resoldrà als mòduls 5 i 7.

  1. Imprimir a dins o retornar?

Aquesta és la decisió de disseny més freqüent en escriure una funció, i la resposta correcta gairebé sempre és la mateixa. Compara una cost_imprimit(dies, tarifa) el cos de la qual és print(f"Cost: {dies * tarifa:.2f} EUR") amb una cost_retornat(dies, tarifa) el cos de la qual és return dies * tarifa:

Criteri Imprimir a dins Retornar el valor
Es pot reutilitzar el resultat? No: se n'ha anat a la pantalla Sí: és un valor com qualsevol altre
Serveix per calcular una altra cosa? No Sí (total = a() + b())
Es pot provar sense mirar la pantalla? No Sí: es compara el valor retornat
Serveix per a un informe, un fitxer, una altra pantalla? No: el format està fixat Sí: ho decideix qui crida
Qui decideix el format? La funció, per sempre Qui la fa servir, en cada cas

Retornar és gairebé sempre millor, i la raó de fons és que una funció que imprimeix ha pres per tu una decisió que no li tocava. cost_imprimit només serveix per mostrar euros amb dos decimals a la pantalla; el dia que vulguis el cost per sumar-lo, comparar-lo o desar-lo, no et val. cost_retornat serveix per a tot això i també per imprimir, perquè sempre pots fer print(f"{cost_retornat(4, 120):.2f} EUR"). La conseqüència pràctica és una regla que reprendrem a Descompondre un programa en funcions: les funcions que calculen no imprimeixen; les que presenten no calculen. I la que sí que ha d'imprimir és precisament la que dialoga amb l'usuari, com demanar_text, que necessita mostrar el missatge per poder preguntar.

  1. *args i **kwargs, per reconèixer-los

Veuràs aquestes dues formes pertot arreu en el codi aliè, així que convé reconèixer-les encara que no les facis servir encara.

Un paràmetre precedit d'un asterisc recull tots els arguments posicionals sobrants, i un de precedit de dos asteriscos recull tots els arguments per nom sobrants:

def anunciar(*missatges):
    """Imprimeix per pantalla tants missatges com se li passin."""
    for missatge in missatges:
        print(f"[AVIS] {missatge}")

anunciar("Reunio a les 10", "Lliurar el cartell", "Trucar a Vidal")
[AVIS] Reunio a les 10
[AVIS] Lliurar el cartell
[AVIS] Trucar a Vidal

La versió amb dos asteriscos es defineix igual —def registrar_esdeveniment(accio, **detalls):— i es crida amb registrar_esdeveniment("alta_tasca", responsable="Nuria", dies=4); a dins, detalls associa cada nom amb el seu valor.

Els noms args i kwargs són pura convenció (d'arguments i keyword arguments); el que fa la feina són els asteriscos. Per dins, *args és una tupla i **kwargs un diccionari, dues estructures del mòdul 5: no cal que les dominis avui, només reconèixer que def funcio(*args, **kwargs): accepta qualsevol cosa. Fes-ne un ús moderat: una funció amb paràmetres explícits es documenta i s'entén molt millor.

  1. TascaFàcil: les funcions de validació

Ha arribat el moment de matar la repetició. Els quatre bucles de validació de la v0.6 es redueixen a tres funcions parametritzades, més una que classifica.

def demanar_text(missatge, obligatori=True):
    """Demana un text per teclat i el retorna ja net d'espais."""
    valor = input(missatge).strip()
    while obligatori and valor == "":
        valor = input("No pot estar buit. " + missatge).strip()
    return valor


def demanar_opcio(missatge, opcions):
    """Demana un valor fins que sigui dins de les opcions admeses."""
    valor = input(missatge).strip().lower()
    while valor not in opcions:
        valor = input(f"No valid. Admesos: {', '.join(opcions)}. ").strip().lower()
    return valor


def demanar_enter(missatge, minim, maxim):
    """Demana un enter i no el retorna fins que sigui entre minim i maxim."""
    text = input(missatge).strip()
    while not text.isdigit() or not minim <= int(text) <= maxim:
        text = input(f"Ha de ser un enter entre {minim} i {maxim}. ").strip()
    return int(text)


def classificar_urgencia(prioritat, dies):
    """Retorna l'etiqueta d'urgencia que correspon a la tasca."""
    if prioritat == "alta" and dies <= 2:
        return "CRITICA"
    if prioritat == "alta":
        return "Urgent"
    if prioritat == "mitjana" and dies <= 3:
        return "Atencio"
    return "Normal"

I així es fan servir des del programa principal:

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

titol = demanar_text("Titol         : ")
responsable = demanar_opcio("Responsable   : ", EQUIP).capitalize()
prioritat = demanar_opcio("Prioritat     : ", PRIORITATS)
dies = demanar_enter("Dies (1-365)  : ", 1, 365)
print(f"{titol} [{classificar_urgencia(prioritat, dies)}]")

Repassa el que ha passat, perquè és molt:

  • Quatre bucles de validació repetits han quedat en tres funcions, cadascuna escrita una sola vegada. Si demà vols canviar el missatge de retret, el canvies en un lloc i canvia a tots.
  • El programa principal es llegeix com una llista d'intencions. La mecànica del while ha desaparegut de la vista sense desaparèixer del programa.
  • demanar_opcio serveix per al responsable, per a la prioritat i per a l'opció del menú, tres coses que a la v0.6 eren tres blocs diferents. Aquest és el poder d'un paràmetre: opcions converteix una funció en una família de funcions. I obligatori=True deixa oberta la porta a demanar una dada opcional el dia que calgui, sense tocar cap crida actual.
  • classificar_urgencia retorna, no imprimeix. Per això la podem ficar dins d'una f-string, i demà la podríem fer servir també per decidir un color o per ordenar tasques.

Fixa't a més en un detall de repartiment de responsabilitats: demanar_opcio normalitza a minúscules, però el .capitalize() del responsable s'aplica fora, a qui crida. La funció s'ocupa de validar; el format de presentació és assumpte del programa. I observa que demanar_enter retorna ja un int, no un text: converteix una vegada, a l'únic lloc on consta que la dada és convertible, aplicant el marc «presència, tipus, domini» de 02-04 de cap a peus.

Errors Comuns i Consells

Oblidar el return. La funció calcula bé i retorna None. Símptoma: TypeError: unsupported operand type(s) for +: 'NoneType' and 'int' una mica més endavant, a la línia que intenta fer servir el resultat.

Confondre print amb return. Escriure print(dies * tarifa) dins de la funció i després cost = calcular_cost(...) deixa cost a None, encara que el número correcte hagi sortit per pantalla. Veure el valor a la consola no és el mateix que tenir-lo.

Nombre d'arguments incorrecte. TypeError: ... missing 1 required positional argument o takes 2 positional arguments but 3 were given. El missatge diu quants n'esperava i quants n'ha rebut: compta'ls a la definició.

Posar un paràmetre amb defecte abans d'un d'obligatori. SyntaxError immediat: els opcionals van al final, sempre. I no facis servir una llista com a valor per defecte: def f(x, historial=[]) comparteix la mateixa llista entre totes les crides. Encara no manegues llistes; recorda-ho per quan arribi 05-01.

Posar codi després del return. No s'executa mai. Si l'has escrit, és que volies una altra cosa.

Consell: escriu primer la crida. Abans d'implementar, escriu com t'agradaria fer-la servir: dies = demanar_enter("Dies: ", 1, 365). Si aquesta línia es llegeix bé, la signatura és bona; si t'obliga a explicar-la, canvia-la abans d'escriure el cos.

Consell: dos o tres paràmetres és el còmode. A partir de quatre, la crida es torna un jeroglífic i convé fer servir arguments per nom —o preguntar-se si aquella funció no estarà fent dues coses.

Exercicis

Exercici 1: Predir i explicar

Digues què imprimeix cada bloc i per què. Hi ha una errada de disseny amagada.

def mitjana_dies(a, b):
    print((a + b) / 2)

def mitjana_dies_2(a, b):
    return (a + b) / 2

x = mitjana_dies(4, 6)
y = mitjana_dies_2(4, 6)
print(x)
print(y)
print(y * 2)

Exercici 2: Pressupost de l'estudi

Escriu tres funcions amb docstring d'una línia:

  • cost_tasca(dies, tarifa=110.0): retorna el cost d'una tasca; la tarifa per defecte és 110 euros al dia.
  • aplicar_descompte(cost, percentatge=0): retorna el cost amb el descompte aplicat. Si el percentatge no és entre 0 i 50, retorna el cost sense tocar.
  • resum_pressupost(titol, dies, tarifa=110.0, descompte=0): fa servir les dues anteriors i retorna dos valors: el text de resum i l'import final.

Després, calcula el pressupost de «Cartell fira del llibre», 4 dies, tarifa per defecte i 10 % de descompte, i imprimeix el resum des del programa principal (no des de dins de les funcions).

Exercici 3: Reparar la funció

Aquesta funció té tres defectes. Troba'ls, explica'ls i reescriu-la.

def classificar(dies, prioritat="alta", llindar):
    if prioritat == "alta":
        print("Urgent")
    elif dies > 10:
        return "Llarg termini"
    return "Normal"
    print("Classificacio acabada")

Solucions

Solució 1.

5.0
None
5.0
10.0

mitjana_dies(4, 6) imprimeix 5.0 des de dins i retorna None, així que x val None. mitjana_dies_2(4, 6) no imprimeix res però retorna 5.0, que es desa a y. Després print(x) mostra None, print(y) mostra 5.0 i print(y * 2) mostra 10.0. L'errada de disseny és mitjana_dies: en imprimir en comptes de retornar, el seu resultat no es pot reutilitzar. Comprova que x * 2 donaria TypeError, mentre que y * 2 funciona sense problema.

Solució 2.

def cost_tasca(dies, tarifa=110.0):
    """Retorna el cost brut d'una tasca de la duracio indicada."""
    return dies * tarifa


def aplicar_descompte(cost, percentatge=0):
    """Retorna el cost amb descompte; ignora percentatges fora de 0-50."""
    if not 0 <= percentatge <= 50:
        return cost
    return cost * (1 - percentatge / 100)


def resum_pressupost(titol, dies, tarifa=110.0, descompte=0):
    """Retorna el text de resum i l'import final del pressupost."""
    brut = cost_tasca(dies, tarifa)
    final = aplicar_descompte(brut, descompte)
    text = f"{titol}: {dies} dies x {tarifa:.2f} = {brut:.2f} EUR"
    return text, final


detall, import_final = resum_pressupost("Cartell fira del llibre", 4, descompte=10)
print(detall)                                          # ... 4 dies x 110.00 = 440.00 EUR
print(f"Total amb descompte: {import_final:.2f} EUR")  # Total amb descompte: 396.00 EUR

Tres detalls: descompte=10 es passa per nom per saltar-se tarifa sense repetir el seu valor per defecte; cap de les tres funcions no imprimeix, així que totes tres són reutilitzables; i la comparació encadenada 0 <= percentatge <= 50 és la de 03-01, dins d'una guarda amb return primerenc.

Solució 3.

Defecte Explicació
llindar sense defecte va darrere de prioritat="alta" SyntaxError en carregar el fitxer
La primera branca imprimeix en comptes de retornar La funció retorna None quan la prioritat és alta
El print final és darrere d'un return Codi mort: no s'executa mai
def classificar(dies, llindar, prioritat="alta"):
    """Retorna la classificacio d'una tasca segons prioritat i duracio."""
    if prioritat == "alta":
        return "Urgent"
    if dies > llindar:
        return "Llarg termini"
    return "Normal"

El paràmetre obligatori passa davant, la primera branca retorna com les altres, el codi mort desapareix i de passada llindar es fa servir de debò, que a l'original ni tan sols apareixia al cos.

Conclusió

Les teves funcions ja dialoguen. Els paràmetres són els buits que declares al def; els arguments són els valors que lliures a la crida, i poden anar per posició o per nom, sent aquesta segona forma la que fa llegibles les crides amb booleans o amb moltes dades. Un paràmetre pot portar valor per defecte, i llavors es torna opcional, amb la regla que els opcionals van al final i mai no són mutables. De l'altra banda, return lliura un valor i acaba la funció a l'acte; n'hi pot haver diversos en branques diferents, serveix com a clàusula de guarda per sortir aviat, i admet diversos valors separats per comes que es desempaqueten en rebre'ls. I la regla que ordena tota la resta: una funció que calcula retorna, no imprimeix, perquè el valor serveix per a tot i la pantalla només per mirar.

TascaFàcil ha fet el salt més gran de la seva història. Els quatre bucles de validació duplicats s'han convertit en demanar_text, demanar_opcio i demanar_enter, tres funcions que s'escriuen una vegada i es fan servir des de qualsevol lloc, i classificar_urgencia calcula una etiqueta sense embrutar la pantalla. El programa principal ha passat de ser un embolic de while a una llista d'intencions llegible. Però en reunir totes aquestes funcions en un fitxer apareixen preguntes noves i molt concretes. Per què demanar_opcio pot llegir la constant PRIORITATS sense que la hi passem? Què passaria si dins d'una funció hi assignés titol = "un altre": canviaria el titol del programa principal? I per què la variable valor de demanar_text no existeix fora? Tot això és una sola qüestió —on viu cada nom— i té nom propi: l'àmbit. És la lliçó vinent: Àmbit de variables.

© Copyright 2026. Tots els drets reservats