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

  1. Crear diccionaris
  2. Accedir a valors: claudàtors i get()
  3. Afegir, modificar i eliminar (del, pop)
  4. Les tres vistes: keys(), values(), items()
  5. Iterar sobre diccionaris
  6. Diccionaris imbricats
  7. Comprensions de diccionaris
  8. 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'atura

Els 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)                        # None

Fixa'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.9

Só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: camp

L'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-se

Despré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

  • KeyError per confiar en els claudàtors: si la clau pot no existir (entrada d'usuari), fes servir get() o comprova amb in abans. Reserva d[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 (del dins del for) llança RuntimeError. Recorre una còpia de les claus: for t in list(cataleg):.
  • Confondre in sobre claus amb in sobre valors: "12.50" in preus mira claus. Per a valors: 12.50 in preus.values().
  • Oblidar que d2 = d1 és un àlies: idèntic a les llistes. I copy() é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

  1. 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.
  2. Informe de reposició. Amb el catàleg canònic complet (els 4 llibres), construeix mitjançant una comprensió de diccionari el dict a_reposar amb els títols amb estoc menor que 5 i com a valor quantes unitats falten fins a 5. Resultat esperat: {"L'Odissea": 1, 'Faust': 5}.
  3. Venda segura. Escriu vendre(cataleg, cercat) que faci servir cercar_llibre() per localitzar el títol (tolerant a majúscules), retorni None si el llibre no existeix o està esgotat i, si hi ha estoc, el decrementi i retorni el preu de soci (convenció de Papyrus, amb DESCOMPTE_SOCI i IVA_LLIBRES). Prova amb "hamlet" (ha de retornar 9.83 i deixar l'estoc a 5) i amb "faust" (ha de retornar None sense 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

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