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
- Crear tuples (inclosa la tupla d'un element)
- Immutabilitat: què vol dir i per a què serveix
- Desempaquetatge, també amb
*per a "la resta" - Retorn múltiple de funcions
- Tuples com a registres lleugers: el llibre com a tupla
- Aperitiu:
namedtuple - 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 iterableEl 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) # TrueImmutabilitat: 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 assignmentPer 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 desempaquetaEl 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 sempreObserva 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 1Tuples 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:
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 igualQueda'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) | Sí |
| 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 unstr. 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çaTypeError. 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çaValueError: too many values to unpack. Compta les variables, o fes servir*restasi la longitud és variable. - Creure que
*restaretorna 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
- Tiquet amb retorn múltiple. Escriu
linia_tiquet(titol, base, soci=False)que retorni la tupla(titol, total, estalvi)ontotalé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) iestalvila 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. - Migració a llista de tuples. Parteix de les tres llistes paral·leles clàssiques de Papyrus i construeix amb
zip()la llista de tuplescataleg_fitxes. Després afegeix "Faust" (21.00, 0) amb un solappendi imprimeix el catàleg amb desempaquetatge alfor, marcant els esgotats. - El més car i el més barat. Sobre
cataleg_fitxesde l'exercici 2, fes servirmax()imin()ambkey=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: HamletError comú aquí: escriure key=llibre[1] sense lambda — llibre 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
- 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
