Tanquem el mòdul amb la construcció més característica de Python. A les lliçons anteriors hauràs notat un patró repetit: crear una llista nova recorrent-ne una altra — els preus del catàleg amb IVA, els títols en majúscules per a les etiquetes, els llibres que encara tenen estoc. Aquest patró de tres o quatre línies (crear llista buida, recórrer, transformar o filtrar, afegir) és tan freqüent que Python li dedica una sintaxi pròpia: les comprensions de llistes (list comprehensions), que el condensen en una sola línia llegible. Dominar quan usar-les — i quan no — és un dels trets d'identitat del codi "pythònic".
Contingut
- El patró que comprimirem
- Sintaxi bàsica:
[expressio for element in iterable] - Filtrar amb
ifal final - Transformar amb
if/elsea l'expressió - Comprensions imbricades (amb moderació)
- Comprensió vs. bucle: comparació directa
- Quan NO usar una comprensió
- Un cop d'ull a les comprensions de diccionaris i conjunts
El patró que comprimirem
L'Anna vol la llista de preus del catàleg amb l'IVA de llibres inclòs. Amb el que saps de la lliçó de bucles, ho faries així:
IVA_LLIBRES = 0.04
preus = [12.50, 9.95, 15.90]
preus_amb_iva = [] # 1. llista buida de partida
for preu in preus: # 2. recorrer l'original
preus_amb_iva.append(preu * (1 + IVA_LLIBRES)) # 3. transformar i afegir
print(preus_amb_iva)Sortida:
Apareix aquí append(), el mètode que afegeix un element al final d'una llista — l'estudiarem amb detall al mòdul 4; per ara n'hi ha prou amb aquesta idea. El patró complet té sempre les mateixes tres peces: llista buida → bucle → append d'una transformació. Python permet expressar exactament això en una línia.
Sintaxi bàsica: [expressio for element in iterable]
La mateixa llista de preus amb IVA, com a comprensió:
IVA_LLIBRES = 0.04
preus = [12.50, 9.95, 15.90]
preus_amb_iva = [preu * (1 + IVA_LLIBRES) for preu in preus]
print(preus_amb_iva) # [13.0, 10.348, 16.536]Anatomia de la línia, de dins cap enfora:
| Peça | A l'exemple | Paper |
|---|---|---|
for element in iterable |
for preu in preus |
El mateix encapçalament de bucle que ja coneixes |
expressio |
preu * (1 + IVA_LLIBRES) |
Què es guarda a la llista nova per cada element |
[ ... ] |
els claudàtors | Indiquen "construeix una llista amb tot això" |
Es llegeix de manera gairebé natural: "una llista amb preu * (1 + IVA) per a cada preu dins de preus". L'ordre d'escriptura sorprèn al principi (l'expressió va abans del for), però reflecteix el que t'interessa: primer què obtens, després d'on surt.
Més exemples ràpids amb material de Papyrus:
titols = ["L'Odissea", "Hamlet", "El Quixot"]
# Titols en majuscules per a les etiquetes de les prestatgeries
etiquetes = [titol.upper() for titol in titols]
# ["L'ODISSEA", 'HAMLET', 'EL QUIXOT']
# Longitud de cada titol (per calcular l'ample de la taula del menu)
longituds = [len(titol) for titol in titols]
# [9, 6, 9]
# Els primers 5 numeros de tiquet del dia
tiquets = [f"T-{n:03d}" for n in range(1, 6)]
# ['T-001', 'T-002', 'T-003', 'T-004', 'T-005'](upper() passa una cadena a majúscules; com lower() a la lliçó anterior, és un mètode de str que veurem a fons al mòdul 4.) Observa el tercer exemple: l'iterable pot ser un range, una cadena, una altra llista... qualsevol cosa recorrible amb for. I l'expressió pot ser tan simple com el mateix element ([x for x in dades], que copia la llista) o tan elaborada com una f-string.
Filtrar amb if al final
Segona peça: de vegades no vols transformar tots els elements, sinó quedar-te només amb alguns. Afegeix un if després del for:
Només els elements que compleixin la condició passen a la llista nova. El clàssic de Papyrus: quins llibres tenen estoc?
titols = ["L'Odissea", "Hamlet", "El Quixot", "Faust"]
estocs = [4, 6, 8, 0]
disponibles = [titols[i] for i in range(len(titols)) if estocs[i] > 0]
print(disponibles) # ["L'Odissea", 'Hamlet', 'El Quixot']I combinant transformació + filtre en una sola comprensió — preus amb IVA, però només dels llibres que costen menys de 15 €:
IVA_LLIBRES = 0.04
preus = [12.50, 9.95, 15.90]
economics_amb_iva = [p * (1 + IVA_LLIBRES) for p in preus if p < 15]
print(economics_amb_iva) # [13.0, 10.348]Es llegeix: "el preu amb IVA, per a cada p dins de preus, si p < 15". El filtre s'aplica primer (entra aquest element?) i l'expressió després (què en guardo?). El Quixot (15.90) no supera el filtre, així que ni tan sols se'n calcula l'IVA.
A l'if pots usar tot el de la lliçó 02-01: condicions compostes (if p < 15 and p > 10), truthiness (if titol per descartar cadenes buides), not...
respostes = ["Hamlet", "", "Faust", ""]
# Netejar entrades buides d'un formulari de comandes
comandes_valides = [r for r in respostes if r]
print(comandes_valides) # ['Hamlet', 'Faust']Transformar amb if/else a l'expressió
Compte, aquest cas es confon molt amb l'anterior. L'if al final filtra (alguns elements no entren). Però si vols que tots els elements entrin, triant entre dos valors segons una condició, el que necessites és l'operador ternari de la lliçó 02-01, col·locat a l'expressió:
Exemple: el llistat del menú de Papyrus ha de mostrar tots els títols, marcant els esgotats.
titols = ["L'Odissea", "Hamlet", "El Quixot", "Faust"]
estocs = [4, 6, 8, 0]
llistat = [
titols[i] if estocs[i] > 0 else f"{titols[i]} (ESGOTAT)"
for i in range(len(titols))
]
print(llistat)
# ["L'Odissea", 'Hamlet', 'El Quixot', 'Faust (ESGOTAT)']Les dues posicions de l'if, cara a cara:
| Forma | Posició de l'if |
else? |
Efecte | Mida de la llista resultat |
|---|---|---|---|---|
[expr for x in dades if cond] |
Després del for |
No l'admet | Filtra elements | ≤ que l'original |
[a if cond else b for x in dades] |
A l'expressió (ternari) | Obligatori | Tria el valor de cada element | Igual que l'original |
Truc mnemotècnic: if sense else al final = porter (decideix qui entra); if amb else al principi = transformador (decideix en què es converteix cadascun). I sí, es poden combinar tots dos en una mateixa comprensió, tot i que la llegibilitat comença a ressentir-se'n:
# Amb descompte de soci els barats, amb IVA tots, i fora els esgotats
DESCOMPTE_SOCI = 0.05
preus = [12.50, 9.95, 15.90, 21.00]
estocs = [4, 6, 8, 0]
finals = [
round(p * (1 - DESCOMPTE_SOCI), 2) if p < 15 else round(p, 2)
for i, p in enumerate(preus)
if estocs[i] > 0
]
print(finals) # [11.88, 9.45, 15.9]Fixa't també que una comprensió llarga es pot (i s'ha de) partir en diverses línies, alineant el for i l'if — continua sent una sola expressió entre claudàtors. I que enumerate() funciona dins de les comprensions igual que als bucles normals.
Comprensions imbricades (amb moderació)
Igual que els bucles s'imbriquen, una comprensió admet diversos for seguits. Equival a bucles imbricats llegits d'esquerra a dreta (el primer for és el bucle exterior):
# Totes les combinacions format-idioma que Papyrus pot demanar al distribuidor
formats = ["tapa dura", "butxaca"]
idiomes = ["ca", "en"]
combinacions = [f"{format_llibre} ({idioma})" for format_llibre in formats for idioma in idiomes]
print(combinacions)
# ['tapa dura (ca)', 'tapa dura (en)', 'butxaca (ca)', 'butxaca (en)']El seu equivalent amb bucles, perquè vegis la correspondència exacta:
combinacions = []
for format_llibre in formats: # primer for de la comprensio
for idioma in idiomes: # segon for de la comprensio
combinacions.append(f"{format_llibre} ({idioma})")Un altre ús de la imbricació és una comprensió dins d'una altra, per exemple per generar una estructura tabular (una llista de llistes):
# Graella d'ubicacions: 2 passadissos x 3 estants
ubicacions = [[f"P{p}-E{e}" for e in range(1, 4)] for p in range(1, 3)]
print(ubicacions)
# [['P1-E1', 'P1-E2', 'P1-E3'], ['P2-E1', 'P2-E2', 'P2-E3']]Advertència sincera: dos for en una comprensió és el límit raonable. Tres o més, o imbricacions amb filtres i ternaris alhora, produeixen línies que ningú (ni tu d'aquí a un mes) no desxifra a la primera. En aquests casos, els bucles imbricats clàssics de la lliçó 02-02 són la millor eina. Comprimir no és l'objectiu; comunicar, sí.
Comprensió vs. bucle: comparació directa
Posem les dues versions costat a costat amb l'exemple complet de Papyrus — preus finals per a en Lluís (soci) dels llibres disponibles:
DESCOMPTE_SOCI = 0.05
IVA_LLIBRES = 0.04
preus = [12.50, 9.95, 15.90, 21.00]
estocs = [4, 6, 8, 0]
# ---- Versio bucle: 5 linies ----
preus_lluis = []
for i, p in enumerate(preus):
if estocs[i] > 0:
final = p * (1 - DESCOMPTE_SOCI) * (1 + IVA_LLIBRES)
preus_lluis.append(round(final, 2))
# ---- Versio comprensio: 1 expressio ----
preus_lluis = [
round(p * (1 - DESCOMPTE_SOCI) * (1 + IVA_LLIBRES), 2)
for i, p in enumerate(preus)
if estocs[i] > 0
]
print(preus_lluis) # [12.35, 9.83, 15.71] en tots dos casosQuè guanya cada versió?
| Criteri | Bucle for clàssic |
Comprensió |
|---|---|---|
| Línies de codi | Més | Menys |
| Es llegeix com | Recepta pas a pas | Declaració: "vull aquesta llista" |
| Passos intermedis (prints de depuració, diverses operacions) | Fàcils d'afegir | No hi caben |
break/continue |
Disponibles | No existeixen dins d'una comprensió |
| Efectes col·laterals (imprimir, modificar altres variables) | Naturals | Contraindicats |
| Velocitat | Bona | Lleugerament més gran (l'append està optimitzat internament) |
L'avantatge real de la comprensió no és estalviar tecles: és declarar la intenció. [t.upper() for t in titols] diu "una llista amb els títols en majúscules" d'un cop d'ull; el bucle equivalent obliga a llegir tres línies per deduir el mateix. Per això, per al patró transformar/filtrar una col·lecció en una llista nova, la comprensió és la forma preferida a la comunitat Python.
Quan NO usar una comprensió
Tan important com saber usar-les és saber contenir-se. Senyals que toca tornar al bucle clàssic:
- No estàs construint una llista. Si l'objectiu és imprimir, acumular un total o demanar
input(), usa un bucle. Escriure[print(t) for t in titols]funciona, però crea una llista inútil deNonei confon el lector: les comprensions són per produir dades, no per fer coses. - La línia no hi cap o no s'entén. Si necessites més de dues o tres peces (dos
for, un filtre i un ternari, per exemple) o superes amb claredat els 79-99 caràcters de PEP 8 fins i tot partint-la, un bucle amb noms intermedis serà més clar. - Necessites parar a mitges o saltar elements amb lògica complexa. No hi ha
breaknicontinueen una comprensió; si els trobes a faltar, és un bucle. - Hi ha diversos passos dependents. Calcular un valor, comprovar-lo, ajustar-lo i després guardar-lo són diverses sentències; encabir-les en una expressió sol requerir trucs il·legibles.
- Estàs depurant. Un bucle admet
print()intermedis per inspeccionar valors; una comprensió, no. De vegades convé "desplegar" temporalment una comprensió a bucle per investigar un problema i tornar-la a plegar després.
Regla final: escriu la comprensió, rellegeix-la com si la veiés una altra persona i, si has de pensar més d'un parell de segons per entendre-la, converteix-la en bucle. Ningú no ha retret mai un bucle clar.
Un cop d'ull a les comprensions de diccionaris i conjunts
La mateixa idea funciona amb altres estructures de dades de Python que estudiaràs al mòdul 4. Només com a aperitiu:
titols = ["L'Odissea", "Hamlet", "El Quixot"]
preus = [12.50, 9.95, 15.90]
# Comprensio de DICCIONARI (claus i dos punts): titol -> preu
tarifa = {titols[i]: preus[i] for i in range(len(titols))}
# {"L'Odissea": 12.5, 'Hamlet': 9.95, 'El Quixot': 15.9}
# Comprensio de CONJUNT (claus sense dos punts): inicials uniques
inicials = {titol[0] for titol in titols}
# {'L', 'H', 'E'}Els diccionaris (parelles clau-valor) i els conjunts (col·leccions sense duplicats) mereixen les seves pròpies lliçons; quan hi arribem al mòdul 4, les seves comprensions et resultaran familiars: són exactament aquesta sintaxi amb {} en lloc de []. De moment, queda't amb la idea que el patró que has après avui és general.
Errors Comuns i Consells
- Confondre l'
if-filtre amb l'if/else-ternari:[x for x in dades if cond]filtra;[a if cond else b for x in dades]transforma. Escriure[x for x in dades if cond else y]ésSyntaxError: l'iffinal no admetelse. - Usar comprensions pels seus efectes (
[print(x) for x in ...]): tècnicament vàlid, estilísticament incorrecte. Si no fas servir la llista resultant, és un bucle. - Fer ombra a variables: la variable de la comprensió és local a ella, però si li poses el mateix nom que una altra variable del programa (
preu = 100i després[preu for preu in preus]), el codi es torna confús. Usa noms diferents i descriptius. - Imbricar de més: dos
forés el màxim còmode. Si dubtes, desplega a bucles. - Oblidar que produeix una llista nova: la comprensió no modifica la llista original;
preuscontinua intacta després de[p * 2 for p in preus]. Si vols conservar el resultat, assigna'l a una variable. - Consell: quan una comprensió s'allargui, parteix-la en diverses línies dins dels claudàtors (expressió /
for/if, una peça per línia). Python ho permet i la llegibilitat millora moltíssim. - Consell: en llegir una comprensió aliena, comença pel
for(què recorre?), segueix per l'if(què deixa passar?) i acaba per l'expressió (què construeix?). És l'ordre en què s'executa.
Exercicis
Exercici 1: PVP del catàleg
Donada preus = [12.50, 9.95, 15.90] i la constant IVA_LLIBRES = 0.04, crea amb una comprensió la llista pvp amb cada preu amb IVA arrodonit a 2 decimals (usa round(valor, 2)). Després, amb una altra comprensió, crea pvp_soci aplicant a més DESCOMPTE_SOCI = 0.05 sobre el preu base, només per a preus base inferiors a 15 € (els altres no han d'aparèixer a la llista).
Exercici 2: etiquetes de l'aparador
Donades titols = ["L'Odissea", "Hamlet", "El Quixot", "Faust"] i estocs = [4, 6, 8, 0], crea amb una sola comprensió la llista d'etiquetes de l'aparador: tots els títols en majúscules, i els que estiguin esgotats amb el sufix " - PROPERAMENT". Resultat esperat: ["L'ODISSEA", 'HAMLET', 'EL QUIXOT', 'FAUST - PROPERAMENT'].
Exercici 3: de comprensió a bucle (i criteri)
Aquesta comprensió funciona, però és difícil de llegir:
(a) Reescriu-la com a bucle for clàssic amb noms de variables descriptius. (b) Explica en una frase per què en aquest cas el bucle és preferible. Nota: zip() emparella dues llistes element a element — s'estudia al mòdul 3; per a aquest exercici en tens prou sabent que for t, s in zip(titols, estocs) recorre títol i estoc alhora.
Solucions
Solució 1:
IVA_LLIBRES = 0.04
DESCOMPTE_SOCI = 0.05
preus = [12.50, 9.95, 15.90]
pvp = [round(p * (1 + IVA_LLIBRES), 2) for p in preus]
print(pvp) # [13.0, 10.35, 16.54]
pvp_soci = [
round(p * (1 - DESCOMPTE_SOCI) * (1 + IVA_LLIBRES), 2)
for p in preus
if p < 15
]
print(pvp_soci) # [12.35, 9.83]La primera és una transformació pura (mateixa longitud que l'original). La segona combina filtre (if p < 15, que descarta El Quixot) i transformació (descompte sobre la base i després IVA, el mateix ordre de càlcul que vam usar a la lliçó 02-01).
Solució 2:
titols = ["L'Odissea", "Hamlet", "El Quixot", "Faust"]
estocs = [4, 6, 8, 0]
etiquetes = [
titols[i].upper() if estocs[i] > 0 else titols[i].upper() + " - PROPERAMENT"
for i in range(len(titols))
]
print(etiquetes)
# ["L'ODISSEA", 'HAMLET', 'EL QUIXOT', 'FAUST - PROPERAMENT']Aquí l'if/else va a l'expressió (ternari) perquè tots els títols han d'aparèixer: no filtrem, triem el format de cadascun. Versió encara més neta, aplicant el sufix amb el ternari: [titols[i].upper() + ("" if estocs[i] > 0 else " - PROPERAMENT") for i in range(len(titols))].
Solució 3:
titols = ["L'Odissea", "Hamlet", "El Quixot", "Faust"]
estocs = [4, 6, 8, 0]
resultat = []
for titol, estoc in zip(titols, estocs):
if len(titol) > 6 and estoc != 1:
if estoc > 3:
resultat.append(titol.upper())
else:
resultat.append(titol.lower())
print(resultat) # ["L'ODISSEA", 'EL QUIXOT']Amb aquestes dades, Hamlet (6 lletres) i Faust (5 lletres) no superen el filtre len(titol) > 6, i els dos títols llargs tenen estoc alt, així que tots dos surten en majúscules. (b) El bucle és preferible perquè la comprensió original acumula quatre peces (ternari + for + zip + filtre compost) en una línia: el lector ha de desxifrar alhora què filtra i què transforma, mentre que el bucle separa cada decisió a la seva pròpia línia amb noms clars. És exactament el criteri de "quan NO usar una comprensió": si costa més d'un parell de segons entendre-la, desplega-la.
Conclusió
Amb les comprensions de llistes has tancat el cercle d'aquest mòdul: [expressio for x in iterable if condicio] reuneix en una sola línia declarativa el que has après per separat — la transformació d'un bucle for, el filtre d'un if i l'elecció d'un ternari. Saps filtrar (if al final), transformar (if/else a l'expressió), imbricar amb prudència i, sobretot, reconèixer quan un bucle clàssic comunica millor. També has vist que la mateixa sintaxi s'estén a diccionaris i conjunts, un avançament del mòdul 4.
El mòdul 2 acaba aquí, i Papyrus ho nota: els seus scripts ja decideixen (if/elif/else), repeteixen (for, while), busquen i paren (break, else de bucle), conversen mitjançant menu.py (while True + match) i generen llistes de preus i etiquetes en una línia. Però tot aquest codi viu solt, repetit d'script en script: la cerca del menú, el càlcul del preu de soci, la validació d'entrades... Cada millora obliga a tocar diversos llocs. La solució és empaquetar cada tasca amb nom propi i reutilitzar-la on calgui: això són les funcions, protagonistes del mòdul 3, on reorganitzarem el menú de Papyrus peça a peça i descobriràs que ja fas servir funcions (print, len, enumerate...) des del primer dia.
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
