Les cadenes t'acompanyen des de la primera línia del curs (print("Benvingut a Papyrus")), i mètodes com upper(), lower() o isdigit() han anat apareixent de passada. Ha arribat el moment de tractar-les com es mereixen: com una seqüència immutable de caràcters amb la caixa d'eines més rica de Python. En aquesta lliçó sistematitzem els seus mètodes, espremem les f-strings més enllà del :.2f del mòdul 1, i resolem un problema real de Papyrus: quan en Lluís tecleja " el quixot " al menú, el programa ha d'entendre "El Quixot" — la normalització d'entrades que cercar_llibre() fa a mitges des de fa temps.
Contingut
- Les cadenes com a seqüències: indexació, llescat,
in - Netejar i trossejar:
strip(),split(),join() - Cercar i substituir:
replace(),find()vsindex(),startswith()/endswith() - Majúscules i minúscules:
lower(),casefold(),title(),upper() - Classificar i farcir:
isdigit(),isalpha(),isalnum(),zfill() - Immutabilitat i les seves conseqüències pràctiques
- F-strings avançades: milers, percentatges, dates i
= - Normalització d'entrades a Papyrus
- Construir cadenes eficientment:
join()vs concatenació en bucle
Les cadenes com a seqüències
Tot el que has après a 04-01 sobre seqüències aplica a les cadenes — cada caràcter és un element:
botiga = "Papyrus"
print(botiga[0]) # P → indexacio des de 0
print(botiga[-1]) # s → negativa des del final
print(botiga[:4]) # Papy → llescat: [inici:fi:pas]
print(botiga[::-1]) # surypaP → invertir, l'idiom de sempre
print(len(botiga)) # 7
print("pyr" in botiga) # True → in cerca SUBCADENES, no nomes caracters
for lletra in botiga[:3]:
print(lletra) # P, a, p → iterable com qualsevol sequenciaDiferència clau amb les llistes: in sobre cadenes troba subcadenes completes ("Qui" in "El Quixot" és True), no només elements solts.
Netejar i trossejar: strip(), split(), join()
El trio imprescindible per processar text d'usuaris i de fitxers (en traurem partit de debò al mòdul 6):
entrada = " El Quixot "
print(entrada.strip()) # 'El Quixot' → sense espais als extrems
print(entrada.lstrip()) # 'El Quixot ' (nomes esquerra) / rstrip(): nomes dreta
print("--Faust--".strip("-")) # 'Faust' → strip accepta quins caracters pelar
linia = "Faust;21.00;0"
camps = linia.split(";")
print(camps) # ['Faust', '21.00', '0'] → de cadena a LLISTA
frase = "el quixot de cervantes"
print(frase.split()) # ['el', 'quixot', 'de', 'cervantes'] → sense argument: per espais
titols = ["L'Odissea", "Hamlet", "Faust"]
print(", ".join(titols)) # "L'Odissea, Hamlet, Faust" → de LLISTA a cadena
print(" | ".join(titols)) # "L'Odissea | Hamlet | Faust"split() i join() són inversos: l'un trosseja, l'altre cus. Fixa't en la sintaxi de join(), que despista al principi: es crida sobre el separador, i rep la llista. Tots els elements han de ser str (converteix abans amb una comprensió: ", ".join(str(p) for p in preus)).
Cercar i substituir
titol = "El Quixot"
print(titol.replace("El", "Don")) # 'Don Quixot' → retorna una cadena NOVA
print(titol.find("Qui")) # 3 → index de la primera aparicio
print(titol.find("xyz")) # -1 → NO trobat: -1, sense error
print(titol.index("Qui")) # 3 → igual que find...
# titol.index("xyz") # ...pero llanca ValueError si no hi es
print(titol.startswith("El ")) # True → comenca per...?
print(titol.endswith("xot")) # True → acaba en...?
print("banana".count("na")) # 2 → aparicions d'una subcadena| Mètode | Si no ho troba... | Fes-lo servir quan... |
|---|---|---|
find(sub) |
Retorna -1 |
L'absència és un cas normal |
index(sub) |
Llança ValueError |
L'absència és un bug que ha de fallar aviat |
Et sona el patró? És el mateix criteri que get() vs claudàtors als dicts i discard() vs remove() als sets. Python és consistent en la seva filosofia. Compte amb el clàssic if titol.find("x"): — si la subcadena és a la posició 0, find retorna 0, que és fals. Compara sempre explícitament: if titol.find("x") != -1: (o millor, fes servir in).
Majúscules i minúscules
titol = "el quixot"
print(titol.upper()) # 'EL QUIXOT'
print(titol.lower()) # 'el quixot'
print(titol.title()) # 'El Quixot' → Primera Lletra De Cada Paraula
print(titol.capitalize()) # 'El quixot' → nomes la primera de tot
print("STRASSE".lower()) # 'strasse'
print("straße".casefold()) # 'strasse' → casefold es un lower() "agressiu"casefold() vs lower(): per al català gairebé no es nota, però casefold() està dissenyat específicament per comparar text ignorant majúscules en qualsevol idioma (la ß alemanya es plega a "ss", per exemple). Regla pràctica: per mostrar, lower()/title(); per comparar, casefold(). Cap dels dos no modifica la cadena original — de seguida veurem per què.
Classificar i farcir
print("42".isdigit()) # True → nomes digits (ho vas fer servir per validar input al M2)
print("4.2".isdigit()) # False → el punt no es un digit
print("Faust".isalpha()) # True → nomes lletres
print("Faust3".isalnum()) # True → lletres i/o digits
print(" ".isspace()) # True → nomes espais en blanc
print("7".zfill(3)) # '007' → farceix amb zeros a l'esquerra
print("LLUIS-1".split("-")[1].zfill(3)) # '001' → normalitzar codis de socizfill() és or per a codis i identificadors de longitud fixa: els socis de Papyrus són LLUIS-001, no LLUIS-1. Per farcir amb altres caràcters existeixen ljust(), rjust() i center(), tot i que per presentar en pantalla les f-strings (secció següent) solen ser més clares.
Immutabilitat i les seves conseqüències pràctiques
Com les tuples, les cadenes són immutables. Cap mètode no les modifica: tots retornen una cadena nova.
titol = "el quixot"
titol.upper() # correcte... pero no es guarda enlloc
print(titol) # 'el quixot' → sense canvis: el resultat s'ha perdut!
titol = titol.upper() # l'idiom correcte: reassignar
print(titol) # 'EL QUIXOT'
titol[0] = "x" # TypeError: no hi ha assignacio per indexConseqüències pràctiques:
- Reassigna sempre:
s = s.strip().lower(). Cridar un mètode "a l'aire" no fa res de visible. - Encadenar mètodes és segur i idiomàtic: cadascun retorna la cadena nova sobre la qual es crida el següent:
" EL QUIXOT ".strip().lower().title()→'El Quixot'. - Les cadenes poden ser claus de dict i elements de set (són hashables) — tot el catàleg de 04-03 en depèn.
- Concatenar en bucle és car: cada
+=crea una cadena nova copiant tot l'anterior. La solució, al final de la lliçó.
F-strings avançades
Del mòdul 1 coneixes {x:.2f} i les alineacions {x:<12} / {x:>6}. La minisintaxi de format dona molt més de si:
from datetime import date
recaptacio_anual = 48231.5
quota_socis = 0.62
avui = date(2026, 7, 13)
preu = 15.90
print(f"{recaptacio_anual:,.2f}") # '48,231.50' → separador de milers
print(f"{recaptacio_anual:,.2f} EUR".replace(",", ".")) # estil europeu: '48.231.50 EUR'
print(f"{quota_socis:.1%}") # '62.0%' → multiplica per 100 i afegeix %
print(f"{avui:%d/%m/%Y}") # '13/07/2026' → format de data (datetime, 03-05)
print(f"{preu:>8.2f}") # ' 15.90' → amplada 8, dreta, 2 decimals
print(f"{'TOTAL':^20}") # ' TOTAL ' → centrat en 20
print(f"{preu=}") # 'preu=15.9' → el = imprimeix nom I valor: depuracio| Especificador | Efecte | Exemple |
|---|---|---|
:, |
Separador de milers | f"{48231.5:,.2f}" → 48,231.50 |
:.1% |
Percentatge amb 1 decimal | f"{0.62:.1%}" → 62.0% |
:%d/%m/%Y |
Format de data/hora | f"{avui:%d/%m/%Y}" |
:>8.2f / :<8 / :^8 |
Alinear en amplada 8 | tiquets alineats |
{expr=} |
Nom i valor alhora | f"{estoc=}" → estoc=4 |
El = mereix un amor especial: print(f"{cataleg['Faust']['estoc']=}") et diu què estàs mirant i quant val, sense escriure l'etiqueta dues vegades. És l'eina de depuració ràpida fins que arribi el mòdul 9.
Normalització d'entrades a Papyrus
Ajuntem les peces. El problema: en Lluís tecleja títols com " el quixot ", "HAMLET" o "faust", i el catàleg canònic fa servir claus exactes. La solució: una funció de normalització única i oficial a papyrus_utils.py:
def normalitzar_titol(text):
"""Prepara un titol teclejat per l'usuari per comparar-lo amb el cataleg."""
return text.strip().casefold()
def cercar_llibre(cataleg, cercat):
"""Retorna la clau canonica del cataleg o None. Versio definitiva."""
objectiu = normalitzar_titol(cercat)
for titol in cataleg:
if normalitzar_titol(titol) == objectiu:
return titol
return None
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},
}
print(cercar_llibre(cataleg, " el quixot ")) # 'El Quixot'
print(cercar_llibre(cataleg, "L'ODISSEA")) # "L'Odissea" → normalitzada: "l'odissea"
print(cercar_llibre(cataleg, "dracula")) # NoneDues decisions de disseny que convé subratllar:
- La normalització viu en una funció: si demà cal treure també els accents, es toca un sol lloc.
- Es normalitzen les dues parts de la comparació (entrada i clau) — fixa't que la clau
"L'Odissea"es converteix en"l'odissea", apòstrof inclòs —, però el catàleg conserva les seves claus boniques per mostrar. Normalitzar només una banda és el bug més habitual.
Construir cadenes eficientment: join() vs concatenació en bucle
Per muntar el tiquet de Papyrus podríem concatenar línia a línia... però cada += copia tota la cadena acumulada (la immutabilitat obliga):
# Versio ingenua: funciona, pero copia i copia i copia
tiquet = ""
for titol, fitxa in cataleg.items():
tiquet += f"{titol:<12} {fitxa['preu']:>6.2f} EUR\n" # cadena nova a cada volta
# Versio idiomatica: acumular en LLISTA i cosir una sola vegada al final
linies = [f"— Tiquet de {'Papyrus'} —"]
for titol, fitxa in cataleg.items():
linies.append(f"{titol:<12} {fitxa['preu']:>6.2f} EUR")
tiquet = "\n".join(linies)
print(tiquet)Amb 4 llibres la diferència és invisible; amb 100 000 línies d'un fitxer de vendes (mòdul 6), la versió amb join() és ordres de magnitud més ràpida. El patró "acumula en llista, uneix amb join" és dels que distingeixen codi principiant de codi professional.
Errors Comuns i Consells
- Oblidar reassignar:
s.strip()senses =no fa res de visible. Les cadenes són immutables; tots els mètodes en retornen una de nova. if s.find(x):— retorna0(fals) si és al principi i-1(verdader!) si no hi és. Fes servirinper a pertinença i!= -1si necessites la posició.join()amb no-cadenes:", ".join([12.5, 9.95])llançaTypeError. Converteix abans:", ".join(str(x) for x in ...).- Normalitzar només una banda de la comparació:
entrada.lower() == "El Quixot"mai no serà cert. Normalitza totes dues, amb la mateixa funció. isdigit()no valida floats:"4.2".isdigit()ésFalse. Per a preus teclejats necessitaràs una altra estratègia (el mòdul 7 la resoldrà ambtry/except).title()i els connectors:"el quixot de cervantes".title()→'El Quixot De Cervantes'— capitalitza també "De" (i darrere d'un apòstrof:"l'odissea".title()→"L'Odissea", que aquí quadra de casualitat). Per a títols editorials perfectes caldrà lògica pròpia; no ho donis per resolt.- Consell: quan processis text d'usuari, normalitza a la frontera (tan bon punt el reps) i treballa ja sempre amb la forma neta. Evita anar repartint
strip()per tot el codi.
Exercicis
- Parser d'altes. L'Anna rep altes de llibres com
" faust ; 21.00 ; 0 ". Escriuparsejar_alta(linia)que retorni la tupla(titol, preu, estoc)amb el títol en formattitle(), el preu com afloati l'estoc com aint, tolerant espais al voltant de cada camp. Prova-la i incorpora el resultat al catàleg canònic amb una sola assignació. - Tiquet professional. Genera amb el patró llista +
join()un tiquet per a la compra d'en Lluís (1 "El Quixot" i 1 "Hamlet", preus de soci 15.71 i 9.83): capçalera centrada en 24 caràcters amb el nom de la botiga, una línia per llibre (títol a l'esquerra en 14, quantia a la dreta en 8 amb 2 decimals), una línia de separació de guions i el total. Extra: mostra l'estalvi del soci com a percentatge amb:.1%sobre la suma de preus sense descompte (16.54 + 10.35). - Validador de codis de soci. Els codis vàlids tenen el format
NOM-NNN(lletres, guió, exactament 3 dígits). Escriues_soci_valid(codi)que normalitzi (strip().upper()), facisplit("-"), i validi ambisalpha(),isdigit()ilen(). Ha d'acceptar" lluis-001 "i rebutjar"LLUIS001","LLUIS-1"i"LL2IS-001".
Solucions
# Exercici 1
def parsejar_alta(linia):
"""De 'titol ; preu ; estoc' (amb espais) a (titol, float, int)."""
titol, preu, estoc = linia.split(";") # desempaquetatge de la llista (3 camps!)
return titol.strip().title(), float(preu.strip()), int(estoc.strip())
titol, preu, estoc = parsejar_alta(" faust ; 21.00 ; 0 ")
cataleg[titol] = {"preu": preu, "estoc": estoc}
print(cataleg["Faust"]) # {'preu': 21.0, 'estoc': 0}Fixa't: float() i int() ja toleren espais exteriors, però l'strip() explícit documenta la intenció i protegeix el title() del títol.
# Exercici 2
NOM_BOTIGA = "Papyrus"
compra = [("El Quixot", 15.71), ("Hamlet", 9.83)]
sense_descompte = 16.54 + 10.35
linies = [f"{NOM_BOTIGA:^24}"]
total = 0.0
for titol, quantia in compra:
linies.append(f"{titol:<14}{quantia:>8.2f} EUR")
total += quantia
linies.append("-" * 24)
linies.append(f"{'TOTAL':<14}{total:>8.2f} EUR")
estalvi = (sense_descompte - total) / sense_descompte
linies.append(f"Estalvi de soci: {estalvi:.1%}")
print("\n".join(linies))# Exercici 3
def es_soci_valid(codi):
"""Format NOM-NNN: lletres, guio, exactament 3 digits."""
parts = codi.strip().upper().split("-")
if len(parts) != 2:
return False
nom, numero = parts
return nom.isalpha() and numero.isdigit() and len(numero) == 3
print(es_soci_valid(" lluis-001 ")) # True
print(es_soci_valid("LLUIS001")) # False (sense guio)
print(es_soci_valid("LLUIS-1")) # False (no son 3 digits)
print(es_soci_valid("LL2IS-001")) # False (el nom no es alfabetic)Error comú: validar amb len(parts) != 2 després de desempaquetar — el desempaquetatge ja hauria llançat ValueError amb "LLUIS001". Comprova la longitud primer, com aquí.
Conclusió
Les cadenes han passat de companyes silencioses a eina dominada: són seqüències immutables (tot mètode retorna una cadena nova — reassigna sempre), es netegen amb strip(), es trossegen i cusen amb split()/join(), es cerquen amb el criteri find() vs index() que ja coneixies de dicts i sets, i es comparen amb casefold(). Les f-strings avançades (:,, :.1%, dates, =) eleven tiquets i informes de Papyrus a nivell professional, normalitzar_titol() ha deixat cercar_llibre() en la seva versió definitiva, i el patró llista + join() t'estalvia el cost ocult de concatenar en bucle. El mòdul ja té totes les estructures fonamentals; l'última lliçó puja un esglaó: el mòdul collections — la namedtuple promesa a 04-02, Counter, defaultdict, deque — i la taula de criteri definitiva per triar estructura sense dubtar.
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
