Aquest és el moment que el curs fa dos mòduls que prepara. Les llistes paral·leles de Papyrus van obligar a malabarismes amb zip() i índexs; la llista de tuples de la lliçó anterior va unir les dades de cada llibre, però continuava identificant els camps per posició i exigia recórrer tot el catàleg per trobar un títol. El diccionari (dict) resol totes dues coses: associa claus amb valors i permet accedir a qualsevol valor directament per la seva clau, sense bucles ni índexs. Al final d'aquesta lliçó executarem el gran refactor: el catàleg de Papyrus serà, per fi, una sola estructura.
Contingut
- Crear diccionaris
- Accedir a valors: claudàtors i
get() - Afegir, modificar i eliminar (
del,pop) - Les tres vistes:
keys(),values(),items() - Iterar sobre diccionaris
- Diccionaris imbricats
- Comprensions de diccionaris
- El gran refactor: el catàleg de Papyrus com a
dict
Crear diccionaris
Un diccionari és una col·lecció de parells clau → valor. S'escriu entre claus, amb clau: valor separats per comes:
preus = {
"L'Odissea": 12.50,
"Hamlet": 9.95,
"El Quixot": 15.90,
}
buit = {} # dict buit (compte: NO es un set buit!)
tambe_buit = dict()
des_de_parells = dict([("Faust", 21.00)]) # a partir d'una llista de tuples (clau, valor)
amb_kwargs = dict(dilluns=3, dimarts=1) # claus com a keywords (nomes si son identificadors)Regles del joc:
- Les claus han de ser úniques i immutables (hashables): cadenes, nombres, tuples... mai llistes. Si repeteixes una clau, l'última assignació guanya.
- Els valors poden ser qualsevol cosa: nombres, cadenes, llistes, altres diccionaris.
- Des de Python 3.7 els diccionaris conserven l'ordre d'inserció — recórrer-los retorna les claus en l'ordre en què es van afegir.
Accedir a valors: claudàtors i get()
print(preus["Hamlet"]) # 9.95 → acces directe per clau, sense recorrer res
print(preus["Dracula"]) # KeyError: 'Dracula' → la clau no existeix, el programa s'aturaEls claudàtors són taxatius: clau inexistent, KeyError. Per a consultes on l'absència és un cas normal (tenim aquest llibre?), get() retorna None — o el valor per defecte que tu triïs — en comptes de fallar:
print(preus.get("Dracula")) # None: no hi es, pero no explota
print(preus.get("Dracula", 0.0)) # 0.0: valor per defecte a mida
print(preus.get("Hamlet", 0.0)) # 9.95: si existeix, get() retorna el valor real
if "Faust" in preus: # in comprova CLAUS, no valors
print("Fitxat")Recordes el sentinella None de 03-02? get() és la mateixa filosofia integrada al llenguatge.
| Situació | Fes servir |
|---|---|
| La clau ha d'existir; la seva absència és un bug | d[clau] (que falli aviat i clar) |
| L'absència és un cas normal del negoci | d.get(clau) o d.get(clau, defecte) |
| Només necessites saber si hi és | clau in d |
Afegir, modificar i eliminar
Els diccionaris són mutables, com les llistes — i amb les mateixes conseqüències d'àlies vs còpia (d2 = d1 no copia; d1.copy() sí, superficialment).
preus = {"L'Odissea": 12.50, "Hamlet": 9.95, "El Quixot": 15.90}
preus["Faust"] = 21.00 # AFEGIR: assignar a una clau nova la crea
preus["Hamlet"] = 10.50 # MODIFICAR: assignar a una clau existent la sobreescriu
preus["Hamlet"] = 9.95 # (la deixem com estava)
del preus["Faust"] # ELIMINAR: del esborra el parell; KeyError si no existeix
preu = preus.pop("El Quixot") # pop: esborra I retorna el valor
print(preu) # 15.9
rescat = preus.pop("Dracula", None) # amb 2n argument, no falla si no existeix
print(rescat) # NoneFixa't en la simetria amb les llistes: pop() també "treu i retorna", però aquí per clau. La mateixa sintaxi d'assignació serveix per crear i per modificar: no hi ha append() perquè no hi ha posicions, hi ha claus.
Les tres vistes: keys(), values(), items()
preus = {"L'Odissea": 12.50, "Hamlet": 9.95, "El Quixot": 15.90}
print(preus.keys()) # dict_keys(["L'Odissea", 'Hamlet', 'El Quixot'])
print(preus.values()) # dict_values([12.5, 9.95, 15.9])
print(preus.items()) # dict_items([("L'Odissea", 12.5), ('Hamlet', 9.95), ...])
print(list(preus.keys())) # convertibles a llista quan ho necessitis
print(sum(preus.values())) # 38.35 → valor total del cataleg (a 1 ut.)
print(max(preus.values())) # 15.9Són vistes dinàmiques, no còpies: si el diccionari canvia, les vistes ho reflecteixen. I observa items(): retorna parells com a tuples (clau, valor) — les tuples de la lliçó anterior apareixen pertot arreu.
Iterar sobre diccionaris
# 1) Iterar directament recorre les CLAUS
for titol in preus:
print(titol, "->", preus[titol])
# 2) La forma idiomatica: items() + desempaquetatge de tupla
for titol, preu in preus.items():
print(f"{titol:<12} {preu:>6.2f} EUR")
# 3) Nomes valors
total = 0.0
for preu in preus.values():
total += preu
# 4) Ordenat per preu: sorted + lambda sobre els items (03-03 en accio)
for titol, preu in sorted(preus.items(), key=lambda parell: parell[1]):
print(titol, preu)La forma 2 és la que veuràs (i escriuràs) el 90 % de les vegades: items() et dona la tupla i el for la desempaqueta, exactament com feies amb zip() — però sense llistes paral·leles al darrere.
Diccionaris imbricats
Els valors poden ser al seu torn diccionaris. És la peça que ens faltava perquè la fitxa d'un llibre tingui camps amb nom:
fitxa_faust = {"preu": 21.00, "estoc": 0}
print(fitxa_faust["preu"]) # 21.0 → "preu" crida el que es; llibre[1] no deia res
cataleg = {
"L'Odissea": {"preu": 12.50, "estoc": 4},
"Hamlet": {"preu": 9.95, "estoc": 6},
}
print(cataleg["Hamlet"]["estoc"]) # 6 → primer claudator: llibre; segon: campL'accés encadenat cataleg[titol][camp] es llegeix de fora cap a dins. Amb get() pots encadenar amb xarxa: cataleg.get("Dracula", {}).get("estoc", 0) retorna 0 sense explotar.
Comprensions de diccionaris
Les vam presentar com a aperitiu al mòdul 2; ara són eina oficial. Sintaxi: {clau: valor for element in iterable}.
IVA_LLIBRES = 0.04
DESCOMPTE_SOCI = 0.05
preus = {"L'Odissea": 12.50, "Hamlet": 9.95, "El Quixot": 15.90, "Faust": 21.00}
# Tarifa de soci calculada de cop (convencio de Papyrus)
tarifa_soci = {t: round(p * (1 - DESCOMPTE_SOCI) * (1 + IVA_LLIBRES), 2)
for t, p in preus.items()}
print(tarifa_soci)
# {"L'Odissea": 12.35, 'Hamlet': 9.83, 'El Quixot': 15.71, 'Faust': 20.75}
# Amb filtre: nomes els llibres de menys de 15 EUR
economics = {t: p for t, p in preus.items() if p < 15}
# De dues llistes paralleles a dict en una linia (ens servira al refactor)
titols = ["L'Odissea", "Hamlet"]
estocs = [4, 6]
inventari = {t: s for t, s in zip(titols, estocs)} # {"L'Odissea": 4, 'Hamlet': 6}El gran refactor: el catàleg de Papyrus com a dict
Ha arribat el moment. Reunim totes les peces i reescrivim el cor de Papyrus. Abans (mòdul 3):
cataleg = ["L'Odissea", "Hamlet", "El Quixot"]
preus = [12.50, 9.95, 15.90]
estocs = [4, 6, 8]
# Cercar = recorrer; mostrar = zip(); alta/baixa = tocar 3 llistes sense equivocar-seDesprés — la nova estructura canònica del catàleg:
NOM_BOTIGA = "Papyrus"
IVA_LLIBRES = 0.04
DESCOMPTE_SOCI = 0.05
cataleg = {
"L'Odissea": {"preu": 12.50, "estoc": 4},
"Hamlet": {"preu": 9.95, "estoc": 6},
"El Quixot": {"preu": 15.90, "estoc": 8},
"Faust": {"preu": 21.00, "estoc": 0},
}Cada llibre és una sola peça d'informació: la clau és el títol i el valor, la seva fitxa amb camps amb nom. Vegem què els passa a les funcions de papyrus_utils.py i menu.py. cercar_llibre() conserva el seu contracte del mòdul 3 en esperit — cerca sense distingir majúscules, None si no hi és — però ara retorna la clau canònica en comptes d'un índex:
def cercar_llibre(cataleg, cercat):
"""Retorna el titol tal com figura al cataleg, o None si no hi es.
La comparacio ignora majuscules/minuscules i espais dels extrems.
"""
cercat = cercat.strip().lower()
for titol in cataleg: # iterar un dict recorre les seves claus
if titol.lower() == cercat:
return titol # la clau "oficial", a punt per indexar
return None
def mostrar_cataleg(cataleg):
"""Imprimeix el cataleg alineat. Ja no necessita zip(): adeu, llistes paralleles."""
print(f"— Cataleg de {NOM_BOTIGA} —")
for titol, fitxa in cataleg.items():
estat = "ESGOTAT" if fitxa["estoc"] == 0 else f"{fitxa['estoc']} ut."
print(f"{titol:<12} {fitxa['preu']:>6.2f} EUR {estat}")I les operacions del dia a dia de l'Anna queden així de directes:
# Consulta exacta: acces directe, sense recorrer res
print(cataleg["Faust"]["preu"]) # 21.0
# Consulta tolerant (el que tecleja en Lluis al menu)
clau = cercar_llibre(cataleg, " el quixot ")
if clau is not None:
print(f"{clau}: {cataleg[clau]['preu']:.2f} EUR")
# Venda: una sola dada per actualitzar (abans: cercar l'index i tocar la llista correcta)
if cataleg["El Quixot"]["estoc"] > 0:
cataleg["El Quixot"]["estoc"] -= 1
# Alta i baixa: una operacio, impossible desincronitzar
cataleg["Iliada"] = {"preu": 13.75, "estoc": 2}
del cataleg["Iliada"]graph TD
subgraph "Abans: 3 llistes paral·leles"
A["cataleg[2] = 'El Quixot'"] -.índex 2.- B["preus[2] = 15.90"]
A -.índex 2.- C["estocs[2] = 8"]
end
subgraph "Ara: 1 diccionari"
D["'El Quixot'"] --> E["{'preu': 15.90, 'estoc': 8}"]
end
Només cercar_llibre() continua recorrent, perquè la seva tolerància a majúscules ho exigeix; la consulta exacta és instantània (a 04-04 veuràs per què aquesta cerca per clau és tan ràpida). preu_final(base, soci=False, iva=IVA_LLIBRES) no canvia ni una línia: opera sobre el preu base, vingui d'on vingui — aquesta és la recompensa d'haver separat negoci i dades al mòdul 3.
Errors Comuns i Consells
KeyErrorper confiar en els claudàtors: si la clau pot no existir (entrada d'usuari), fes servirget()o comprova ambinabans. Reservad[clau]per a claus que garanteixes.- Fer servir una llista com a clau:
TypeError: unhashable type: 'list'. Les claus han de ser immutables; si necessites una clau composta, fes servir una tupla:("Hamlet", "tapa dura"). {}crea un diccionari, no un conjunt: ho veurem a la propera lliçó, però grava-t'ho ja.- Modificar un dict mentre el recorres (
deldins delfor) llançaRuntimeError. Recorre una còpia de les claus:for t in list(cataleg):. - Confondre
insobre claus ambinsobre valors:"12.50" in preusmira claus. Per a valors:12.50 in preus.values(). - Oblidar que
d2 = d1és un àlies: idèntic a les llistes. Icopy()és superficial: amb fitxes imbricades,copia["Hamlet"]continua sent la mateixa fitxa interior. - Consell de disseny: claus = identitat (el títol), valors = atributs (preu, estoc). Si et descobreixes cercant per valor constantment, potser vas triar malament la clau.
Exercicis
- Migració completa. Escriu una funció
muntar_cataleg(titols, preus, estocs)que rebi les tres llistes paral·leles històriques de Papyrus i retorni el diccionari imbricat canònic (clau = títol →{"preu": ..., "estoc": ...}). Fes-la servir amb les dades clàssiques, afegeix després "Faust" (21.00, estoc 0) amb una sola assignació i imprimeix el resultat. - Informe de reposició. Amb el catàleg canònic complet (els 4 llibres), construeix mitjançant una comprensió de diccionari el dict
a_reposaramb els títols ambestocmenor que 5 i com a valor quantes unitats falten fins a 5. Resultat esperat:{"L'Odissea": 1, 'Faust': 5}. - Venda segura. Escriu
vendre(cataleg, cercat)que faci servircercar_llibre()per localitzar el títol (tolerant a majúscules), retorniNonesi el llibre no existeix o està esgotat i, si hi ha estoc, el decrementi i retorni el preu de soci (convenció de Papyrus, ambDESCOMPTE_SOCIiIVA_LLIBRES). Prova amb"hamlet"(ha de retornar 9.83 i deixar l'estoc a 5) i amb"faust"(ha de retornarNonesense tocar res).
Solucions
# Exercici 1
def muntar_cataleg(titols, preus, estocs):
"""Converteix les tres llistes paralleles en el cataleg canonic."""
return {t: {"preu": p, "estoc": s}
for t, p, s in zip(titols, preus, estocs)} # zip, en el seu ultim servei
cataleg = muntar_cataleg(
["L'Odissea", "Hamlet", "El Quixot"], [12.50, 9.95, 15.90], [4, 6, 8]
)
cataleg["Faust"] = {"preu": 21.00, "estoc": 0}
print(cataleg)Fixa't: la comprensió amb zip() de tres llistes és la cerimònia de comiat de les llistes paral·leles — s'executa una vegada a la migració i no se les torna a veure.
# Exercici 2
a_reposar = {titol: 5 - fitxa["estoc"]
for titol, fitxa in cataleg.items()
if fitxa["estoc"] < 5}
print(a_reposar) # {"L'Odissea": 1, 'Faust': 5}# Exercici 3
IVA_LLIBRES = 0.04
DESCOMPTE_SOCI = 0.05
def vendre(cataleg, cercat):
"""Ven 1 ut. a preu de soci; None si no existeix o esta esgotat."""
clau = cercar_llibre(cataleg, cercat)
if clau is None:
return None
fitxa = cataleg[clau]
if fitxa["estoc"] == 0:
return None
fitxa["estoc"] -= 1 # muta la fitxa compartida: el cataleg ho reflecteix
return round(fitxa["preu"] * (1 - DESCOMPTE_SOCI) * (1 + IVA_LLIBRES), 2)
print(vendre(cataleg, "hamlet")) # 9.83
print(cataleg["Hamlet"]["estoc"]) # 5
print(vendre(cataleg, "faust")) # None (esgotat)Error comú: retornar False en comptes de None per a "no venut". Sigues consistent amb el sentinella None de 03-02: és la convenció de tot el curs.
Conclusió
L'ham llançat al final del mòdul 3 queda tancat: el catàleg de Papyrus ja no són tres llistes per sincronitzar a mà ni una llista per recórrer cercant títols, sinó un diccionari imbricat — {títol: {"preu": ..., "estoc": ...}} — on cada llibre és una sola peça d'informació amb camps amb nom i accés directe. Domines la creació, l'accés amb claudàtors i get(), les altes/baixes amb assignació, del i pop(), les vistes keys()/values()/items(), la iteració idiomàtica, la imbricació i les comprensions. cercar_llibre() i mostrar_cataleg() s'han reescrit sobre la nova estructura sense canviar el seu contracte, i preu_final() ni se n'ha adonat. Queda una pregunta a l'aire: hem dit que consultar cataleg["Faust"] és "instantani" — per què? La resposta la comparteix una altra estructura basada en la mateixa màgia, especialitzada en unicitat i pertinença ultraràpida: el conjunt, protagonista de la propera lliçó.
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
