A la lliçó anterior vas veure que la mutabilitat de les llistes és una arma de doble tall: potent per gestionar l'estoc canviant de Papyrus, perillosa quan un àlies modifica dades que creies fora de perill. Python ofereix la contrapartida exacta: la tupla, una seqüència ordenada i immutable. A més de protegir dades que no han de canviar, la tupla és el mecanisme amb què Python retorna diversos valors des d'una funció — la promesa que vam deixar apuntada a 03-02 i que avui saldem — i el primer pas seriós perquè cada llibre de Papyrus sigui una sola peça d'informació en comptes d'una fila repartida en tres llistes.

Contingut

  1. Crear tuples (inclosa la tupla d'un element)
  2. Immutabilitat: què vol dir i per a què serveix
  3. Desempaquetatge, també amb * per a "la resta"
  4. Retorn múltiple de funcions
  5. Tuples com a registres lleugers: el llibre com a tupla
  6. Aperitiu: namedtuple
  7. Tuples vs llistes: criteri d'elecció

Crear tuples

Una tupla s'escriu amb parèntesis... tot i que en realitat el que la crea és la coma:

llibre = ("L'Odissea", 12.50, 4)     # tupla de 3 elements: la fitxa d'un llibre
punt = 3, 5                           # tambe es una tupla! els parentesis son opcionals
buida = ()                            # tupla buida (aqui si que calen els parentesis)
tambe_buida = tuple()
des_de_llista = tuple([4, 6, 8])      # tuple() converteix qualsevol iterable

El parany clàssic és la tupla d'un sol element: sense coma final no hi ha tupla.

no_es_tupla = ("Faust")      # aixo es nomes un str entre parentesis
si_es_tupla = ("Faust",)     # la coma final la converteix en tupla
tambe = "Faust",             # fins i tot sense parentesis

print(type(no_es_tupla))     # <class 'str'>
print(type(si_es_tupla))     # <class 'tuple'>

Com a seqüències que són, les tuples comparteixen gairebé tota la mecànica de la lliçó anterior: indexació positiva i negativa, llescat, len(), in/not in, count() i index():

llibre = ("L'Odissea", 12.50, 4)
print(llibre[0])       # L'Odissea
print(llibre[-1])      # 4
print(llibre[:2])      # ("L'Odissea", 12.5)  → la llesca d'una tupla es una altra tupla
print(12.50 in llibre) # True

Immutabilitat: què vol dir i per a què serveix

Un cop creada, la tupla no admet canvis: ni assignar per índex, ni append, ni remove, ni sort — aquests mètodes senzillament no existeixen.

llibre = ("L'Odissea", 12.50, 4)
llibre[2] = 3         # TypeError: 'tuple' object does not support item assignment

Per a què voldria l'Anna una cosa que "no es pot tocar"? Precisament per a això:

  • Protecció davant d'errors: les dades fixes de la botiga (nom, coordenades, la parella descompte/IVA) no es poden corrompre per accident. Si algun codi intenta modificar-les, Python avisa a l'instant.
  • Comunicació d'intenció: qui llegeix ("L'Odissea", 12.50, 4) entén que és un registre tancat, no una col·lecció que creix.
  • Claus de diccionari i elements de conjunt: en ser immutables (i per tant hashables), les tuples es poden fer servir on les llistes no — ho aprofitaràs a 04-03 i 04-04.
  • Lleugeresa: Python les pot optimitzar millor que les llistes.

Un matís honest: la immutabilitat és superficial. Si una tupla conté una llista, la tupla no pot canviar quins objectes conté, però la llista interior continua sent mutable:

config = ("Papyrus", [4, 6, 8])   # tupla amb una llista a dins
config[1].append(0)               # legal: mutem la llista, no la tupla
print(config)                     # ('Papyrus', [4, 6, 8, 0])

Desempaquetatge, també amb *

El desempaquetatge assigna els elements d'una tupla a diverses variables de cop. Ja el vas fer servir sense anomenar-lo amb enumerate() i zip(); ara el veiem com a ciutadà de primera:

llibre = ("L'Odissea", 12.50, 4)
titol, preu, estoc = llibre            # tres variables en una linia
print(f"{titol}: {preu:.2f} EUR ({estoc} ut.)")

# Intercanviar variables sense auxiliar: l'idiom mes famos de Python
a, b = 10, 20
a, b = b, a                            # (b, a) es una tupla que es desempaqueta

El nombre de variables ha de coincidir amb el d'elements... llevat que facis servir * per capturar "la resta" en una llista:

vendes_setmana = (3, 1, 0, 2, 5, 7, 4)   # unitats venudes de dilluns a diumenge
dilluns, *entre_setmana, diumenge = vendes_setmana
print(dilluns)          # 3
print(entre_setmana)    # [1, 0, 2, 5, 7]  ← sempre una LLISTA, encara que vingui d'una tupla
print(diumenge)         # 4

primer, *resta = ("L'Odissea", "Hamlet", "El Quixot", "Faust")
print(primer)           # L'Odissea
print(resta)            # ['Hamlet', 'El Quixot', 'Faust']

Només hi pot haver un * per desempaquetatge. Hi reconeixeràs la idea de *args (03-02): és el mateix mecanisme, ara a la banda de l'assignació.

Retorn múltiple de funcions

Aquí saldem el deute de 03-02. Quan una funció "retorna diversos valors", en realitat retorna una tupla que després es desempaqueta. Afegim al nostre repertori una funció de tiquet per a Papyrus:

IVA_LLIBRES = 0.04        # IVA fictici del curs
DESCOMPTE_SOCI = 0.05

def desglossament_venda(base, soci=False):
    """Retorna (base, descompte, iva, total) d'una venda, en euros."""
    descompte = round(base * DESCOMPTE_SOCI, 2) if soci else 0.0
    subtotal = base - descompte
    iva = round(subtotal * IVA_LLIBRES, 2)
    total = round(base * (1 - DESCOMPTE_SOCI) * (1 + IVA_LLIBRES), 2) if soci \
            else round(base * (1 + IVA_LLIBRES), 2)
    return base, descompte, iva, total      # sense parentesis: la coma crea la tupla

# En Lluis (soci) compra "El Quixot" (15.90 EUR de base)
resultat = desglossament_venda(15.90, soci=True)
print(resultat)               # (15.9, 0.8, 0.63, 15.71)  → una unica tupla
print(type(resultat))         # <class 'tuple'>

base, dte, iva, total = desglossament_venda(15.90, soci=True)   # desempaquetatge directe
print(f"Total amb carnet de soci: {total:.2f} EUR")             # 15.71 EUR, com sempre

Observa les dues cares: qui crida pot quedar-se la tupla sencera (resultat) o desempaquetar-la al vol. Si només t'interessa una part, el guió baix és la convenció per a "això ho ignoro":

*_, total = desglossament_venda(12.50, soci=True)
print(total)    # 12.35 → el preu de soci de "L'Odissea", verificat al modul 1

Tuples com a registres lleugers: el llibre com a tupla

I aquí arriba el moviment estratègic del mòdul. Les tres llistes paral·leles es poden convertir en una sola llista de tuples, on cada tupla és la fitxa completa d'un llibre:

# Abans: tres llistes per sincronitzar a ma
# cataleg = ["L'Odissea", "Hamlet", "El Quixot"]; preus = [...]; estocs = [...]

# Ara: una llista de registres (titol, preu, estoc)
cataleg = [
    ("L'Odissea", 12.50, 4),
    ("Hamlet", 9.95, 6),
    ("El Quixot", 15.90, 8),
    ("Faust", 21.00, 0),
]

for titol, preu, estoc in cataleg:              # desempaquetatge al for: adeu zip()
    estat = "ESGOTAT" if estoc == 0 else f"{estoc} ut."
    print(f"{titol:<12} {preu:>6.2f} EUR  {estat}")

Compara-ho amb el zip(cataleg, preus, estocs) del mòdul 3: el resultat és el mateix, però ja no hi ha tres estructures que es puguin desincronitzar — donar d'alta "Faust" és un únic cataleg.append(("Faust", 21.00, 0)). La llista exterior és mutable (el catàleg creix i minva); cada fitxa és immutable (les dades d'un llibre viatgen juntes i protegides). I si baixa l'estoc? En ser immutable, no es modifica la tupla: es reemplaça per una altra:

cataleg[2] = ("El Quixot", 15.90, 7)   # en Lluis compra un exemplar

Aquest reemplaçament complet és incòmode, i a més llibre[1] continua sent un índex mut: res no crida "soc el preu". Ens queden dues millores per endavant.

Aperitiu: namedtuple

La primera millora ja existeix a la biblioteca estàndard (mòdul collections, del qual vam parlar a 03-05): tuples els camps de les quals tenen nom.

from collections import namedtuple

Llibre = namedtuple("Llibre", ["titol", "preu", "estoc"])
odissea = Llibre("L'Odissea", 12.50, 4)

print(odissea.preu)     # 12.5  → per nom, no per index mut
print(odissea[1])       # 12.5  → continua sent una tupla normal
titol, preu, estoc = odissea   # i es desempaqueta igual

Queda't amb la idea; la desenvoluparem a fons a 04-06. La segona millora — cercar un llibre per títol sense recórrer res — és la lliçó 04-03.

Tuples vs llistes: criteri d'elecció

Criteri Llista Tupla
Mutabilitat Mutable: creix, minva, es reordena Immutable: es crea d'una peça
Ús típic Col·lecció homogènia de mida variable (títols, cua de comandes) Registre heterogeni d'estructura fixa (títol + preu + estoc)
Mètodes Molts (append, sort, remove...) Només count() i index()
Clau de dict / element de set? No (no és hashable)
Retorn múltiple de funcions Possible però inusual L'idiom estàndard
Sintaxi [1, 2, 3] (1, 2, 3) o 1, 2, 3

Regla mnemotècnica: llista = "moltes coses del mateix tipus"; tupla = "una cosa amb diverses parts". El catàleg (molts llibres) és una llista; cada llibre (títol, preu, estoc) és una tupla.

Errors Comuns i Consells

  • Oblidar la coma a la tupla d'un element: ("Faust") és un str. Si una funció espera una tupla i li passes això, els errors apareixen lluny de l'origen. Escriu ("Faust",).
  • Intentar mutar una tupla (llibre[2] -= 1) llança TypeError. No és un defecte: és la tupla fent la seva feina. Si necessites canviar valors sovint, aquella peça demanava ser una altra estructura (spoiler: un diccionari).
  • Desempaquetatge desquadrat: a, b = (1, 2, 3) llança ValueError: too many values to unpack. Compta les variables, o fes servir *resta si la longitud és variable.
  • Creure que *resta retorna una tupla: sempre produeix una llista, vingui d'on vingui.
  • Tupla "immutable" amb llistes a dins: la immutabilitat no es propaga a l'interior. Si necessites un registre de debò congelat, evita ficar-hi llistes.
  • Consell: quan una funció teva comenci a retornar 4-5 valors, planteja't si no està demanant un namedtuple (04-06) o un diccionari (04-03) — desempaquetar 5 posicions de memòria és fràgil.

Exercicis

  1. Tiquet amb retorn múltiple. Escriu linia_tiquet(titol, base, soci=False) que retorni la tupla (titol, total, estalvi) on total és el preu final (convenció de Papyrus: base * (1 - DESCOMPTE_SOCI) * (1 + IVA_LLIBRES) si és soci, base * (1 + IVA_LLIBRES) si no, arrodonit a 2 decimals) i estalvi la diferència entre no ser soci i ser-ho. Desempaqueta el resultat per a "Hamlet" (9.95) sent soci i imprimeix una línia de tiquet amb f-string.
  2. Migració a llista de tuples. Parteix de les tres llistes paral·leles clàssiques de Papyrus i construeix amb zip() la llista de tuples cataleg_fitxes. Després afegeix "Faust" (21.00, 0) amb un sol append i imprimeix el catàleg amb desempaquetatge al for, marcant els esgotats.
  3. El més car i el més barat. Sobre cataleg_fitxes de l'exercici 2, fes servir max() i min() amb key=lambda llibre: llibre[1] (repàs de 03-03) per obtenir la fitxa del llibre més car i la del més barat, i desempaqueta-les per imprimir només els seus títols.

Solucions

# Exercici 1
IVA_LLIBRES = 0.04
DESCOMPTE_SOCI = 0.05

def linia_tiquet(titol, base, soci=False):
    """Retorna (titol, total, estalvi) d'una linia de venda."""
    total_normal = round(base * (1 + IVA_LLIBRES), 2)
    total_soci = round(base * (1 - DESCOMPTE_SOCI) * (1 + IVA_LLIBRES), 2)
    total = total_soci if soci else total_normal
    estalvi = round(total_normal - total_soci, 2)
    return titol, total, estalvi

titol, total, estalvi = linia_tiquet("Hamlet", 9.95, soci=True)
print(f"{titol}: {total:.2f} EUR (estalvies {estalvi:.2f} EUR per ser soci)")
# Hamlet: 9.83 EUR (estalvies 0.52 EUR per ser soci)
# Exercici 2
cataleg = ["L'Odissea", "Hamlet", "El Quixot"]
preus = [12.50, 9.95, 15.90]
estocs = [4, 6, 8]

cataleg_fitxes = list(zip(cataleg, preus, estocs))   # zip uneix; list materialitza
cataleg_fitxes.append(("Faust", 21.00, 0))           # una sola alta, impossible desincronitzar

for titol, preu, estoc in cataleg_fitxes:
    estat = "ESGOTAT" if estoc == 0 else f"{estoc} ut."
    print(f"{titol:<12} {preu:>6.2f} EUR  {estat}")

Consell: zip() retorna tuples de per si — la migració de llistes paral·leles a llista de tuples és literalment un list(zip(...)). És l'última vegada que necessitarem zip() per al catàleg.

# Exercici 3
mes_car = max(cataleg_fitxes, key=lambda llibre: llibre[1])
mes_barat = min(cataleg_fitxes, key=lambda llibre: llibre[1])

titol_car, *_ = mes_car
titol_barat, *_ = mes_barat
print(f"Mes car: {titol_car} | Mes barat: {titol_barat}")
# Mes car: Faust | Mes barat: Hamlet

Error comú aquí: escriure key=llibre[1] sense lambdallibre no existeix fora de la lambda; el key necessita una funció, no un valor.

Conclusió

Les tuples completen les llistes amb la peça que els faltava: la immutabilitat. Saps crear-les (recordant la coma de l'element únic), desempaquetar-les — també amb *resta — i has vist que el "retorn múltiple" de les funcions de Python sempre va ser una tupla disfressada, saldant la promesa de 03-02. Sobretot, el catàleg de Papyrus ha fet el seu primer gran pas: de tres llistes paral·leles a una llista de tuples on cada llibre viatja com una unitat, amb namedtuple apuntada com a millora per a 04-06. Però encara hi ha dues molèsties: els camps s'identifiquen per índexs muts i trobar un llibre exigeix recórrer la llista sencera. La propera lliçó presenta l'estructura reina de Python, el diccionari, i amb ella executarem el gran refactor que el mòdul 3 va deixar anunciat: cada llibre accessible directament pel seu títol.

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