A la lliçó anterior, les nostres funcions rebien dades de la manera més simple: un valor per posició per a cada paràmetre. Suficient per començar, però curt per a Papyrus: el càlcul del preu final depèn de si el client és soci o no, i no volem dues funcions gairebé idèntiques (preu_normal() i preu_soci()) quan la lògica és la mateixa amb un matís. Python ofereix un sistema d'arguments extraordinàriament flexible: arguments per posició i per nom, valors per defecte, quantitats variables amb *args i **kwargs, i desempaquetatge a la crida. En aquesta lliçó el recorrem sencer, construïm la funció estrella de la botiga — preu_final(base, soci=False, iva=IVA_LLIBRES) — i saldem el deute que arrosseguem des del mòdul 2: entendre de debò com funciona zip().

Contingut

  1. Arguments posicionals i arguments amb nom (keyword)
  2. Valors per defecte: preu_final() per a socis i no socis
  3. La trampa dels valors per defecte mutables
  4. *args: quantitat variable de posicionals
  5. **kwargs: quantitat variable d'arguments amb nom
  6. L'ordre dels paràmetres a la definició
  7. Desempaquetatge amb * i ** a la crida
  8. Retornar diversos valors (tuples, presentació breu)
  9. Deute saldat: zip() a fons

Arguments posicionals i arguments amb nom

Fins ara hem aparellat arguments amb paràmetres per posició: el primer amb el primer, el segon amb el segon. Python també permet aparellar-los per nom (arguments keyword):

def etiquetar(titol, preu):
    print(f"{titol:<12} {preu:>6.2f} €")

etiquetar("Hamlet", 9.95)               # posicionals
etiquetar(titol="Hamlet", preu=9.95)    # amb nom
etiquetar(preu=9.95, titol="Hamlet")    # amb nom: l'ordre es indiferent
etiquetar("Hamlet", preu=9.95)          # barreja: posicionals primer
etiquetar(preu=9.95, "Hamlet")          # SyntaxError: posicional despres de keyword

Regles i criteri:

  • En una crida mixta, els posicionals van sempre abans que els keyword.
  • Els arguments amb nom fan la crida autoexplicativa: preu_final(12.50, True) obliga a mirar la definició per saber què és aquest True; preu_final(12.50, soci=True) es llegeix sola. Ja vas fer servir aquesta idea al mòdul 2 amb enumerate(cataleg, start=1) i al mòdul 1 amb print(..., sep=", ", end=""): start, sep i end són arguments keyword.
  • Regla pràctica: per posició les dades òbvies i obligatòries; per nom les opcions i banderes.

Valors per defecte

Un paràmetre pot declarar un valor que es fa servir si la crida no el proporciona. És el mecanisme perfecte per a la funció de preus de Papyrus:

IVA_LLIBRES = 0.04
DESCOMPTE_SOCI = 0.05

def preu_final(base, soci=False, iva=IVA_LLIBRES):
    """Retorna el preu final: descompte de soci (si escau) i despres l'IVA."""
    if soci:
        base = base * (1 - DESCOMPTE_SOCI)
    return round(base * (1 + iva), 2)

print(preu_final(12.50))             # 13.0   -> client normal
print(preu_final(12.50, soci=True))  # 12.35  -> Lluis, soci
print(preu_final(12.50, soci=True, iva=0.21))  # 14.37 -> simulacio d'un altre IVA

Desglossament:

  • base no té valor per defecte: és obligatori. Oblidar-lo produeix TypeError: preu_final() missing 1 required positional argument: 'base'.
  • soci=False i iva=IVA_LLIBRES són opcionals: la crida curta preu_final(12.50) cobreix el cas més freqüent (client no soci, IVA de llibres) i els casos especials es demanen explícitament.
  • L'ordre d'operacions respecta la convenció de la botiga des del mòdul 1: primer el descompte, després l'IVA. Els preus de soci del catàleg surten clavats: 12.35, 9.83 i 15.71 € per a L'Odissea, Hamlet i El Quixot.

Un matís que sorprèn: els valors per defecte s'avaluen una sola vegada, quan Python llegeix el def. iva=IVA_LLIBRES pren el valor que IVA_LLIBRES tingui en aquell moment (0.04) i el congela; si més tard reassignessis la constant, el defecte no canviaria. Amb constants i immutables això és inofensiu... però condueix directament a la trampa més famosa de Python.

La trampa dels valors per defecte mutables

Com que el defecte s'avalua una única vegada, fer servir un objecte mutable (una llista, per exemple) com a valor per defecte significa que totes les crides comparteixen el mateix objecte:

def apuntar_comanda(titol, comandes=[]):     # PERILL!
    comandes.append(titol)
    return comandes

print(apuntar_comanda("Faust"))      # ['Faust']
print(apuntar_comanda("Hamlet"))     # ['Faust', 'Hamlet']  <- i aquest Faust?!

La segona comanda "hereta" la primera perquè la llista per defecte és la mateixa a totes dues crides: es va crear en llegir el def i encara hi és, acumulant. L'idioma correcte fa servir None com a sentinella:

def apuntar_comanda(titol, comandes=None):
    """Afegeix el titol a la llista de comandes (o crea una llista nova)."""
    if comandes is None:
        comandes = []               # llista NOVA a cada crida
    comandes.append(titol)
    return comandes

print(apuntar_comanda("Faust"))      # ['Faust']
print(apuntar_comanda("Hamlet"))     # ['Hamlet']  <- correcte
Valor per defecte Segur? Exemples
Immutable False, 0, 0.04, "es", None
Mutable No — es comparteix entre crides [], {} (fes servir el patró None)

*args: quantitat variable de posicionals

De vegades no saps quants valors arribaran. Un paràmetre amb * al davant recull tots els arguments posicionals sobrants:

def total_compra(*quanties):
    """Retorna la suma de totes les quanties rebudes."""
    total = 0.0
    for quantia in quanties:
        total += quantia
    return round(total, 2)

print(total_compra(12.35))               # 12.35 -> Lluis s'emporta un llibre
print(total_compra(12.35, 9.83, 15.71))  # 37.89 -> se n'emporta tres
print(total_compra())                    # 0.0   -> avui nomes mirava
  • Dins de la funció, quanties és una tupla amb zero o més valors, i es recorre amb for com qualsevol seqüència.
  • El nom args és pura convenció (aquí quanties és més expressiu); el que importa és el *.
  • Ja coneixies això sense saber-ho: print("a", "b", "c") accepta qualsevol quantitat d'arguments gràcies a aquest mecanisme.

**kwargs: quantitat variable d'arguments amb nom

El doble asterisc recull els arguments keyword sobrants en un diccionari (parells nom-valor; els diccionaris s'estudien a fons al mòdul 4, aquí només en necessites la idea):

def fitxa_llibre(titol, **dades):
    """Imprimeix una fitxa amb el titol i qualsevol dada extra."""
    print(f"Fitxa de '{titol}'")
    for camp, valor in dades.items():
        print(f"  {camp}: {valor}")

fitxa_llibre("Faust", autor="Goethe", any=1808, estoc=0)
Fitxa de 'Faust'
  autor: Goethe
  any: 1808
  estoc: 0

Cada llibre de Papyrus pot tenir dades diferents (editorial, edició, idioma...) sense canviar la signatura de la funció. dades.items() recorre els parells nom-valor; de nou, el nom kwargs (keyword arguments) és només convenció.

L'ordre dels paràmetres a la definició

Quan combinis diverses categories, l'ordre al def és fix:

def exemple(obligatori, amb_defecte=1, *args, **kwargs):
    ...
Posició Categoria Exemple
1r Posicionals obligatoris base
2n Amb valor per defecte soci=False, iva=IVA_LLIBRES
3r *args *quanties
4t **kwargs **dades

Saltar-se l'ordre (def f(soci=False, base)) és SyntaxError: parameter without a default follows parameter with a default. A la pràctica, la majoria de les teves funcions faran servir només les dues primeres categories — *args/**kwargs són per a casos genuïnament variables, no un comodí per no pensar la signatura.

Desempaquetatge amb * i ** a la crida

Els asteriscos també funcionen en sentit invers: reparteixen una seqüència o un diccionari entre els paràmetres de la crida:

dades_faust = ["Faust", 21.00]
etiquetar(*dades_faust)         # equival a etiquetar("Faust", 21.00)

opcions = {"soci": True, "iva": 0.04}
print(preu_final(21.00, **opcions))   # equival a soci=True, iva=0.04 -> 20.75
  • *sequencia en una crida desempaqueta els seus elements com a arguments posicionals.
  • **diccionari desempaqueta parells nom-valor com a arguments keyword (les claus han de coincidir amb noms de paràmetres).
  • Mnemotècnia: al def, els asteriscos empaqueten (molts arguments → una variable); a la crida, desempaqueten (una variable → molts arguments).

Retornar diversos valors (presentació breu)

return pot lliurar diversos valors separats per comes; tècnicament retorna una tupla (seqüència immutable que veurem a fons al mòdul 4), i l'habitual és desempaquetar-la en rebre-la — el mateix mecanisme de l'assignació múltiple del mòdul 1:

def resum_preu(base, soci=False):
    """Retorna (preu final, estalvi respecte al preu sense descompte)."""
    final = preu_final(base, soci=soci)
    sense_descompte = preu_final(base)
    return final, round(sense_descompte - final, 2)

preu, estalvi = resum_preu(12.50, soci=True)
print(f"Lluis paga {preu} € i estalvia {estalvi} €")   # Lluis paga 12.35 € i estalvia 0.65 €

Amb això en tenim prou per ara; les tuples tindran la seva pròpia lliçó (04-02).

Deute saldat: zip() a fons

Al mòdul 2 vam fer servir zip() de passada; ha arribat el moment d'entendre-la. Papyrus guarda les seves dades en tres llistes paral·leles — l'element i de cadascuna descriu el mateix llibre:

cataleg = ["L'Odissea", "Hamlet", "El Quixot"]
preus = [12.50, 9.95, 15.90]
estocs = [4, 6, 8]

zip() rep diverses seqüències i va produint tuples amb els elements aparellats per posició: primer ("L'Odissea", 12.50, 4), després ("Hamlet", 9.95, 6), etc. És com la cremallera (zipper) d'una jaqueta: uneix una dent de cada costat a cada pas.

for titol, preu, estoc in zip(cataleg, preus, estocs):
    print(f"{titol:<12} {preu:>6.2f} €  estoc {estoc}")

Compara-la amb el patró d'índexs que fèiem servir fins ara:

Amb índexs Amb zip()
Codi for i, titol in enumerate(cataleg): ... preus[i] ... estocs[i] for titol, preu, estoc in zip(...)
Llegibilitat Regular: preus[i] obliga a rastrejar i Alta: cada variable té nom propi
Dona l'índex? No (combina-la amb enumerate si el necessites)
Llistes de longitud diferent? IndexError si la que indexa és més llarga S'atura a la més curta, en silenci

Detalls importants:

  • zip() és peresosa: no construeix una llista de tuples en memòria, les va lliurant a mesura que el for les demana. Si vols veure-les totes de cop: list(zip(cataleg, preus))[("L'Odissea", 12.5), ('Hamlet', 9.95), ('El Quixot', 15.9)]. Conseqüència pràctica: un zip() ja recorregut queda esgotat; crea'n un de nou per recórrer-lo una altra vegada.
  • Que s'aturi a la seqüència més curta és còmode però perillós: si a cataleg hi afegeixes "Faust" i t'oblides d'afegir-ne el preu, el for simplement ignorarà el llibre sense avisar. Des de Python 3.10, zip(cataleg, preus, strict=True) llança un error si les longituds no coincideixen — fixa-t'hi: strict és un argument keyword, com els que acabes d'aprendre.
  • zip() també combina de meravella amb les funcions d'aquesta lliçó:
def imprimir_tarifa_socis(titols, bases):
    """Imprimeix cada titol amb el seu preu de soci."""
    for titol, base in zip(titols, bases):
        print(f"{titol:<12} {preu_final(base, soci=True):>6.2f} €")

imprimir_tarifa_socis(cataleg, preus)
L'Odissea     12.35 €
Hamlet         9.83 €
El Quixot     15.71 €

Aquestes llistes paral·leles comencen a ser fràgils (tres llistes que cal mantenir sincronitzades a mà); al mòdul 4 coneixeràs estructures que agrupen les dades de cada llibre en un sol lloc. Fins llavors, zip() és la cola oficial de Papyrus.

Errors Comuns i Consells

  • Posicional després de keyword a la crida (preu_final(soci=True, 12.50)): SyntaxError. Primer les posicions, després els noms.
  • Repetir un argument (preu_final(12.50, base=12.50)): TypeError: got multiple values for argument 'base'. El valor ha arribat per posició i per nom alhora.
  • [] o {} com a valor per defecte: la trampa estrella. Fes servir None i crea l'objecte a dins. Els linters (com els que suggereix VS Code) ho marquen; fes-los cas.
  • Passar banderes per posició (preu_final(12.50, True)): funciona, però d'aquí a un mes ningú no recordarà què era True. Escriu soci=True.
  • Confondre * al def amb * a la crida: a la definició empaqueta; a la crida desempaqueta. Si dubtes, pregunta't a quin costat del parèntesi ets.
  • Abusar de *args/**kwargs: una signatura explícita documenta i valida sola. Reserva els asteriscos per a funcions genuïnament variàdiques (com total_compra).
  • Oblidar que zip() calla amb longituds diferents: en dades que han d'anar aparellades sí o sí, fes servir strict=True (Python ≥ 3.10).
  • Consell: en dissenyar una signatura, ordena de més a menys important i posa defectes a tot allò que tingui un valor "normal". La crida típica hauria de ser la més curta.

Exercicis

Exercici 1: el tiquet variàdic

Escriu tiquet(client, *quanties, soci=False) que imprimeixi una línia Tiquet de <client>, després cada quantia passada per preu_final(quantia, soci=soci) (una per línia, format :>6.2f), i al final Total: X.XX € amb la suma dels preus finals. Prova-la amb tiquet("Lluis", 12.50, 9.95, 15.90, soci=True) — el total ha de ser 37.89 € — i amb tiquet("Marta", 21.00).

Exercici 2: reposar sense ensurts

L'Anna va escriure aquesta funció per apuntar reposicions pendents i alguna cosa va malament:

def apuntar_reposicio(titol, pendents=[]):
    pendents.append(titol)
    return pendents

Executa apuntar_reposicio("Faust") i després apuntar_reposicio("Hamlet"), observa el problema, explica per què passa i corregeix-la amb el patró del sentinella None.

Exercici 3: tarifa completa amb zip()

Amb cataleg, preus i estocs (més "Faust", 21.00, 0 afegits: fes servir cataleg + ["Faust"], etc., per no modificar les originals), imprimeix amb un sol bucle sobre zip() una taula títol | preu normal | preu soci | estat, on l'estat sigui esgotat si l'estoc és 0 i disponible en cas contrari. Fes servir preu_final() per a tots dos preus.

Solucions

Exercici 1:

def tiquet(client, *quanties, soci=False):
    """Imprimeix el tiquet d'una compra amb preus finals."""
    print(f"Tiquet de {client}")
    total = 0.0
    for quantia in quanties:
        final = preu_final(quantia, soci=soci)
        total += final
        print(f"{final:>6.2f} €")
    print(f"Total: {round(total, 2)} €")

tiquet("Lluis", 12.50, 9.95, 15.90, soci=True)   # 12.35 / 9.83 / 15.71, Total: 37.89 €
tiquet("Marta", 21.00)                           # 21.84, Total: 21.84 €

Com que soci va després de *quanties, només es pot passar per nom — Python no el confondrà mai amb una quantia més. És un truc de disseny molt utilitzat.

Exercici 2: La segona crida retorna ['Faust', 'Hamlet']: totes dues comparteixen la mateixa llista per defecte, creada una sola vegada en llegir el def. Correcció:

def apuntar_reposicio(titol, pendents=None):
    """Afegeix un titol a la llista de reposicions (nova si no es passa)."""
    if pendents is None:
        pendents = []
    pendents.append(titol)
    return pendents

Ara cada crida sense llista rep una llista acabada de crear, i qui ja tingui una llista pròpia pot passar-la: apuntar_reposicio("Hamlet", pendents=la_meva_llista).

Exercici 3:

titols = cataleg + ["Faust"]
bases = preus + [21.00]
unitats = estocs + [0]

print(f"{'Titol':<12} {'Normal':>8} {'Soci':>8}  Estat")
for titol, base, estoc in zip(titols, bases, unitats):
    estat = "esgotat" if estoc == 0 else "disponible"
    print(f"{titol:<12} {preu_final(base):>6.2f} € {preu_final(base, soci=True):>6.2f} €  {estat}")

Cada variable del for rep el seu element aparellat per zip(); el ternari del mòdul 2 resol l'estat en una línia. Sortida: Faust apareix amb 21.84 € / 20.75 € i esgotat.

Conclusió

Les funcions de Papyrus ja parlen un llenguatge ric: arguments posicionals per a l'essencial, keyword per a les opcions (soci=True), valors per defecte per al cas comú — esquivant la trampa dels mutables —, *args/**kwargs per al que és variable i desempaquetatge amb */** a les crides. A més, zip() ha deixat de ser màgia: és la cremallera que recorre en paral·lel les llistes de la botiga, i preu_final(base, soci=False, iva=IVA_LLIBRES) queda establerta com la funció oficial de preus. A la propera lliçó coneixerem una forma abreujada d'escriure funcions diminutes i anònimes — les lambdes — i les posarem a treballar allà on de debò brillen: com a criteri de sorted(), min() i max() per, per fi, ordenar el catàleg de Papyrus per preu.

Curs de Programació en Python

Mòdul 1: Introducció a Python

Mòdul 2: Estructures de Control

Mòdul 3: Funcions i Mòduls

Mòdul 4: Estructures de Dades

Mòdul 5: Programació Orientada a Objectes

Mòdul 6: Gestió de Fitxers

Mòdul 7: Gestió d'Errors i Excepcions

Mòdul 8: Temes Avançats

Mòdul 9: Proves i Depuració

Mòdul 10: Desenvolupament Web amb Python

Mòdul 11: Ciència de Dades amb Python

Mòdul 12: Projecte Final

© Copyright 2026. Tots els drets reservats