El mòdul 3 va acabar assenyalant una esquerda: el catàleg de Papyrus viu repartit en tres llistes paral·leles (cataleg, preus, estocs) que l'Anna ha de mantenir sincronitzades a mà. Abans de resoldre aquesta esquerda amb diccionaris (lliçó 04-03), necessites dominar de debò l'estructura que has estat fent servir des del mòdul 1 gairebé sense mirar-la: la llista. Fins ara has recorregut llistes amb for, enumerate() i zip(), i has fet servir append() de passada; en aquesta lliçó les obrim per dins: indexació, slicing (llescat), tots els seus mètodes importants i —el més delicat— què vol dir que siguin mutables.
Contingut
- Crear llistes
- Indexació positiva i negativa
- Llescat (slicing) a fons
- Mètodes de llistes: afegir, treure i cercar
- Ordenar:
sort()vssorted()ireverse() - Mutabilitat: àlies vs còpia
- Llistes imbricades
- Pertinença:
ininot in
Crear llistes
Una llista és una seqüència ordenada i mutable d'elements, que poden ser de qualsevol tipus (fins i tot barrejats, tot i que no sol ser bona idea).
# Les tres llistes paralleles de Papyrus, tal com van quedar al modul 3
cataleg = ["L'Odissea", "Hamlet", "El Quixot"]
preus = [12.50, 9.95, 15.90]
estocs = [4, 6, 8]
buida = [] # llista buida, a punt per anar-la omplint
tambe_buida = list() # equivalent
des_de_range = list(range(5)) # [0, 1, 2, 3, 4]: list() converteix iterables
lletres = list("Anna") # ['A', 'n', 'n', 'a']: una cadena tambe es iterable[]crea una llista literal;list(iterable)construeix una llista a partir de qualsevol cosa recorrible.- Recorda del mòdul 2 que també existeixen les comprensions:
[p * 1.04 for p in preus]crea una llista nova calculada. Aquí ens centrem a manipular llistes ja existents.
Indexació positiva i negativa
Cada element té una posició (índex) que comença en 0. Els índexs negatius compten des del final: -1 és l'últim element.
cataleg = ["L'Odissea", "Hamlet", "El Quixot"]
print(cataleg[0]) # L'Odissea (primer)
print(cataleg[2]) # El Quixot (tercer)
print(cataleg[-1]) # El Quixot (ultim, sense saber quants n'hi ha)
print(cataleg[-2]) # Hamlet (penultim)| Element | Índex positiu | Índex negatiu |
|---|---|---|
| "L'Odissea" | 0 | -3 |
| "Hamlet" | 1 | -2 |
| "El Quixot" | 2 | -1 |
Accedir a un índex que no existeix (cataleg[10]) provoca un IndexError que atura el programa. Al mòdul 7 aprendràs a capturar aquest error; de moment, comprova-ho abans amb len().
Llescat (slicing) a fons
El slicing (llescat) extreu una subllista nova amb la sintaxi llista[inici:fi:pas]. Regla d'or: l'inici s'inclou, el fi no.
titols = ["L'Odissea", "Hamlet", "El Quixot", "Faust"]
print(titols[0:2]) # ["L'Odissea", 'Hamlet'] indexs 0 i 1
print(titols[1:]) # ['Hamlet', 'El Quixot', 'Faust'] des d'1 fins al final
print(titols[:2]) # ["L'Odissea", 'Hamlet'] des del principi fins a 1
print(titols[:]) # copia completa (ho veurem a mutabilitat!)
print(titols[::2]) # ["L'Odissea", 'El Quixot'] de dos en dos
print(titols[::-1]) # llista invertida, sense tocar l'original
print(titols[-2:]) # ['El Quixot', 'Faust'] els dos ultimsPunts que convé interioritzar:
- Ometre
inicivol dir "des del principi"; ometrefi, "fins al final". - Una llesca mai no dona error per passar-se:
titols[1:100]retorna el que hi hagi, senseIndexError. - Un
pasnegatiu recorre cap enrere;[::-1]és l'idiom clàssic per invertir. - El resultat és sempre una llista nova: modificar-la no afecta l'original.
Mètodes de llistes: afegir, treure i cercar
Els mètodes es criden amb la sintaxi llista.metode(...). La majoria modifiquen la llista al lloc i retornen None — font clàssica de despistes.
| Mètode | Què fa | Retorna |
|---|---|---|
append(x) |
Afegeix x al final |
None |
insert(i, x) |
Insereix x a la posició i, desplaçant la resta |
None |
extend(altra) |
Afegeix tots els elements d'un altre iterable al final | None |
remove(x) |
Elimina la primera aparició de x (error si no hi és) |
None |
pop(i) |
Elimina i retorna l'element a i (per defecte, l'últim) |
l'element |
index(x) |
Posició de la primera aparició de x (error si no hi és) |
int |
count(x) |
Quantes vegades apareix x |
int |
Vegem-los en acció amb l'arribada de "Faust" a Papyrus (de moment amb estoc 0: està esgotat a la distribuïdora):
cataleg = ["L'Odissea", "Hamlet", "El Quixot"]
preus = [12.50, 9.95, 15.90]
estocs = [4, 6, 8]
# Alta de "Faust": cal tocar LES TRES llistes, i en el mateix ordre
cataleg.append("Faust")
preus.append(21.00)
estocs.append(0)
print(cataleg.index("Hamlet")) # 1 → serveix per localitzar el seu preu: preus[1]
print(cataleg.count("Faust")) # 1
# append vs extend: la diferencia importa
novetats = ["Iliada", "Macbeth"]
copia = cataleg[:]
copia.append(novetats) # ["L'Odissea", ..., 'Faust', ['Iliada', 'Macbeth']] llista dins de llista!
copia = cataleg[:]
copia.extend(novetats) # ["L'Odissea", ..., 'Faust', 'Iliada', 'Macbeth'] elements soltsFixa't en el cost de manteniment: donar d'alta un llibre exigeix tres append() coordinats, i un remove() oblidat en una de les llistes desincronitza preus i estocs per sempre. Aquest dolor és deliberat: és l'ham que resoldrem a 04-03.
La cua de comandes
Les llistes també serveixen com a cua: les comandes entren pel final (append) i s'atenen pel principi (pop(0)):
cua_comandes = []
cua_comandes.append("Lluis: El Quixot")
cua_comandes.append("Marta: Hamlet")
cua_comandes.append("Lluis: Faust")
while cua_comandes: # truthiness: llista buida es False
comanda = cua_comandes.pop(0) # treu i retorna la primera
print(f"Atenent -> {comanda}")Funciona, però pop(0) obliga Python a desplaçar tots els elements restants una posició. Per a cues llargues existeix una estructura pensada exactament per a això, collections.deque, que veuràs a 04-06.
Ordenar: sort() vs sorted() i reverse()
Aquesta distinció resumeix la filosofia dels mètodes de llista:
llista.sort() |
sorted(llista) |
|
|---|---|---|
| Què modifica? | La llista original, al lloc | Res: crea una llista nova |
| Què retorna? | None |
La llista ordenada |
| Sobre què funciona? | Només llistes | Qualsevol iterable (tuples, cadenes...) |
| Ús típic | Quan ja no necessites l'ordre original | Quan vols conservar l'original |
preus = [12.50, 9.95, 15.90, 21.00]
barats_primer = sorted(preus) # [9.95, 12.5, 15.9, 21.0]; preus intacte
cars_primer = sorted(preus, reverse=True)
preus.sort() # ara si: preus queda ordenat
resultat = preus.sort() # compte! resultat es None
cataleg = ["L'Odissea", "Hamlet", "El Quixot", "Faust"]
cataleg.reverse() # inverteix al lloc (retorna None)
per_longitud = sorted(cataleg, key=len) # el key= de les lambdes (03-03) tambe viu aquiCompte amb les llistes paral·leles: si l'Anna fa cataleg.sort(), els títols es reordenen però preus i estocs no, i el desastre està servit. Amb zip() (03-02) es pot ordenar tot junt, però és un malabarisme més que els diccionaris faran innecessari.
Mutabilitat: àlies vs còpia
Aquí hi ha la conseqüència més important (i traïdora) del fet que les llistes siguin mutables. Assignar una llista a una altra variable no la copia: crea un àlies, un segon nom per a la mateixa llista.
estocs = [4, 6, 8]
inventari = estocs # ALIES: les dues variables apunten a LA MATEIXA llista
inventari.append(0)
print(estocs) # [4, 6, 8, 0] ← estocs tambe ha "canviat"!
print(estocs is inventari) # True: son el mateix objectegraph LR
estocs --> L["[4, 6, 8, 0]"]
inventari --> L
Per tenir una llista independent cal copiar explícitament:
copia1 = estocs.copy() # metode copy()
copia2 = estocs[:] # llesca completa: idiom equivalent
copia3 = list(estocs) # constructor: tambe copia
copia1.append(99)
print(estocs) # sense canvis: la copia es una altra llistaAixò explica també el parany dels arguments mutables per defecte que vas veure a 03-02: si una funció rep una llista i la modifica, qui la crida nota el canvi, perquè tots dos comparteixen el mateix objecte.
Un matís per deixar apuntat: copy() i [:] fan una còpia superficial (shallow). Si la llista conté altres llistes a dins, es copia la llista exterior però les interiors continuen compartides. Per a còpies profundes existeix copy.deepcopy(); amb saber que existeix ja en tens prou per ara.
Llistes imbricades
Una llista pot contenir llistes. És una manera primitiva d'agrupar la fitxa de cada llibre:
fitxes = [
["L'Odissea", 12.50, 4],
["Hamlet", 9.95, 6],
["El Quixot", 15.90, 8],
]
print(fitxes[1]) # ['Hamlet', 9.95, 6] → la fitxa completa
print(fitxes[1][1]) # 9.95 → primer index: fila; segon: columna
fitxes[2][2] -= 1 # en Lluis compra un Quixot: l'estoc baixa a 7
for titol, preu, estoc in fitxes: # desempaquetatge al for, com amb zip()
print(f"{titol:<12} {preu:>6.2f} EUR ({estoc} ut.)")Ja és un avenç: cada llibre viatja junt i un sol append(["Faust", 21.00, 0]) dona d'alta tot. Però fitxa[1] i fitxa[2] són índexs muts: res no diu que 1 sigui el preu. La lliçó següent (tuples) i sobretot la 04-03 (diccionaris) poliran aquesta idea.
Pertinença: in i not in
Els operadors in / not in comproven si un valor és a la llista, sense bucles a la vista:
cataleg = ["L'Odissea", "Hamlet", "El Quixot", "Faust"]
if "Faust" in cataleg:
print("El tenim fitxat (tot i que potser sense estoc).")
if "Dracula" not in cataleg:
print("Caldria demanar-lo a la distribuidora.")És una comparació exacta: "faust" in cataleg dona False per les majúscules. Per això cercar_llibre() de papyrus_utils.py compara en minúscules; a 04-05 anirem encara més lluny normalitzant text.
Errors Comuns i Consells
- Guardar el resultat de
sort(),append()oreverse():llista = llista.sort()deixallistavalentNone. Aquests mètodes modifiquen al lloc i retornenNone; no reassignis. - Confondre àlies amb còpia:
b = ano copia. Si després mutesbia"canvia sola", aquest és el símptoma. Fes servira.copy()oa[:]. remove()iindex()amb valors inexistents llancenValueError. Protegeix-te ambif x in llista:abans de cridar-los (la gestió elegant d'errors arriba al mòdul 7).- Modificar una llista mentre la recorres amb
forprodueix salts d'elements. Recorre una còpia (for x in llista[:]) o construeix una llista nova amb una comprensió. append(altra_llista)quan voliesextend(altra_llista): el primer fica la llista sencera com un únic element imbricat.- Consell: quan dubtis entre mutar o crear, pregunta't si algú més fa servir aquesta llista. Si l'has rebuda com a argument, crear-ne una de nova (
sorted, llescat, comprensió) és gairebé sempre més segur.
Exercicis
- Alta i baixa sincronitzades. Partint de les tres llistes paral·leles de Papyrus, escriu una funció
alta_llibre(cataleg, preus, estocs, titol, preu, estoc)que afegeixi el llibre a les tres llistes, i unabaixa_llibre(cataleg, preus, estocs, titol)que l'elimini de les tres fent servirindex()ipop(). Prova de donar d'alta "Faust" (21.00, 0) i de baixa "Hamlet". - Llesques del catàleg. Amb
cataleg = ["L'Odissea", "Hamlet", "El Quixot", "Faust"], obtén fent servir només llescat: (a) els dos primers títols, (b) els dos últims, (c) el catàleg invertit, (d) una còpia independent que puguis ordenar alfabèticament sense tocar l'original. - Cua amb prioritat de soci. Simula la cua de comandes com a llista de cadenes
"nom: titol". Les comandes d'en Lluís (soci) s'han de colar: escriu-les ambinsert(0, ...)en comptes d'append(). Atén la cua ambwhileipop(0)mostrant l'ordre final.
Solucions
# Exercici 1
def alta_llibre(cataleg, preus, estocs, titol, preu, estoc):
"""Afegeix un llibre a les tres llistes paralleles (mutant-les)."""
cataleg.append(titol)
preus.append(preu)
estocs.append(estoc)
def baixa_llibre(cataleg, preus, estocs, titol):
"""Elimina un llibre de les tres llistes; retorna el seu preu i estoc."""
if titol not in cataleg:
return None # sentinella, com a 03-02
i = cataleg.index(titol) # mateixa fila a les tres llistes
cataleg.pop(i)
return (cataleg, preus.pop(i), estocs.pop(i))[1:] # (preu, estoc)
cataleg = ["L'Odissea", "Hamlet", "El Quixot"]
preus = [12.50, 9.95, 15.90]
estocs = [4, 6, 8]
alta_llibre(cataleg, preus, estocs, "Faust", 21.00, 0)
print(baixa_llibre(cataleg, preus, estocs, "Hamlet")) # (9.95, 6)
print(cataleg) # ["L'Odissea", 'El Quixot', 'Faust']Fixa't que les funcions muten les llistes rebudes: qui les crida veu els canvis perquè comparteix els objectes. I que cada operació toca tres estructures — l'argument definitiu per a 04-03.
# Exercici 2
cataleg = ["L'Odissea", "Hamlet", "El Quixot", "Faust"]
print(cataleg[:2]) # (a) ["L'Odissea", 'Hamlet']
print(cataleg[-2:]) # (b) ['El Quixot', 'Faust']
print(cataleg[::-1]) # (c) invertit
copia = cataleg[:] # (d) copia independent
copia.sort()
print(copia) # ordenada
print(cataleg) # l'original, intacte# Exercici 3
cua = []
def encuar(cua, client, titol, soci=False):
comanda = f"{client}: {titol}"
if soci:
cua.insert(0, comanda) # els socis es colen al principi
else:
cua.append(comanda)
encuar(cua, "Marta", "Hamlet")
encuar(cua, "Lluis", "El Quixot", soci=True)
encuar(cua, "Pau", "L'Odissea")
while cua:
print("Atenent ->", cua.pop(0))
# Lluis: El Quixot / Marta: Hamlet / Pau: L'OdisseaConsell: insert(0, ...) i pop(0) desplacen tota la llista; per a Papyrus és irrellevant, però recorda-ho quan vegis deque a 04-06.
Conclusió
Les llistes són la navalla suïssa de Python: saps crear-les, indexar-les (també des del final), llescar-les amb [inici:fi:pas], i manejar els seus mètodes distingint els que muten al lloc (append, sort, remove...) de les funcions que creen llistes noves (sorted, llescat). Sobretot, ara entens la mutabilitat: b = a crea un àlies, no una còpia, i aquest detall explica comportaments que fins avui semblaven màgia negra. També n'has vist els límits a Papyrus: les llistes paral·leles exigeixen disciplina de sincronització i les llistes imbricades fan servir índexs muts. El pas següent és la seva germana immutable, la tupla: l'estructura perfecta per a registres que no han de canviar — i la que per fi salda la promesa del retorn múltiple de funcions que vam deixar pendent a 03-02.
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
