A la lliçó anterior vas descobrir que una llista és una seqüència: elements ordenats als quals s'accedeix per índex. El que potser no veies venir és que fas servir una altra seqüència des del mòdul 2 sense anomenar-la així. El text —el tipus str— és exactament això: una seqüència de caràcters. Tot el que vas aprendre sobre índexs, talls i recorreguts amb for funciona igual sobre una cadena, amb una diferència decisiva: la cadena és immutable.

I això no és cap adorn teòric. A TascaFàcil gairebé tot és text: els títols que tecleja la Marta, els noms de l'equip, les prioritats, les línies que més endavant desarem en un fitxer. Manejar bé les cadenes és el que separa un programa que accepta « CARTELL fira » i «cartell Fira» com dues tasques diferents d'un que entén que són la mateixa.

Contingut

  1. La cadena com a seqüència immutable
  2. Índexs, talls i recorregut
  3. Neteja: strip, lstrip, rstrip
  4. Majúscules i minúscules
  5. Cerca dins del text
  6. Comprovació del contingut
  7. Transformació i farciment
  8. Partició i unió: el pont amb les llistes
  9. Multilínia, escapaments i cadenes crues
  10. f-strings, str() i Unicode
  11. Comparar i ordenar textos
  12. Aplicació: títols i codis de tasca a Estudi Alba
  13. Errors comuns i consells
  14. Exercicis
  15. Conclusió

  1. La cadena com a seqüència immutable

Una cadena és una seqüència de caràcters amb un ordre fix. len(), els índexs i l'operador in funcionen igual que en una llista:

titol = "Cartell fira"
print(len(titol))           # 12  (els espais compten)
print(titol[0], titol[-1])  # C a
print("fira" in titol)      # True

La diferència és aquí:

titol[0] = "M"
# TypeError: 'str' object does not support item assignment

Una cadena no es pot modificar. Ni un caràcter. Tot el que sembla modificar text en realitat crea una cadena nova i deixa l'original intacta:

titol = "cartell fira"
titol.upper()               # crea "CARTELL FIRA" i la llenca: no es guarda
print(titol)                # cartell fira  -> continua igual
titol = titol.upper()       # ara si: reassignem el nom
print(titol)                # CARTELL FIRA
Llista Cadena
Índexs, talls i for Sí Sí
Modificar un element Sí No: TypeError
Mètodes que modifiquen al lloc append, sort… Cap
Mètodes que retornen alguna cosa nova sorted, copy Tots

Retén aquesta idea, perquè explica l'error número u amb cadenes: si no guardes el resultat, no ha passat res. I a canvi de la restricció hi ha un avantatge: en ser immutables, les cadenes es poden fer servir com a claus de diccionari, cosa que veuràs a 05-03.

  1. Índexs, talls i recorregut

Tot el de 05-01 s'aplica sense canvis, llevat que aquí el resultat d'un tall és una altra cadena:

client = "Forn Sole"
print(client[0:4])          # Forn
print(client[-4:])          # Sole
print(client[:3].upper())   # FOR   -> tres primeres lletres en majuscules
print(client[::-1])         # eloS nroF  (al reves)

Per recórrer caràcter a caràcter, el for directe; i amb enumerate si necessites la posició:

for lletra in "Alba":
    print(lletra, end=" ")          # A l b a

for i, lletra in enumerate("Alba"):
    print(f"{i}:{lletra}", end=" ") # 0:A 1:l 2:b 3:a

El patró comptador de 03-02 funciona igual aquí: recórrer el text sumant 1 cada vegada que la lletra compleix una condició. Gairebé sempre, això sí, hi ha un mètode que ho resol en una línia —comptar espais a mà dóna el mateix que titol.count(" ")—, així que recorre caràcter a caràcter només quan la condició sigui pròpia teva.

  1. Neteja: strip, lstrip, rstrip

Quan el text ve d'input() o d'un fitxer, gairebé sempre porta brutícia al voltant: espais, tabuladors o el salt de línia final. La família strip la treu.

Mètode Què treu Exemple
.strip() Espais dels dos extrems " alba ".strip() → "alba"
.lstrip() Només per l'esquerra " alba".lstrip() → "alba"
.rstrip() Només per la dreta "alba\n".rstrip() → "alba"
.strip(chars) Qualsevol d'aquests caràcters "**alba**".strip("*") → "alba"

Amb entrada = " Cartell fira del llibre \n", f"[{entrada.strip()}]" imprimeix [Cartell fira del llibre]. Compte amb un malentès freqüent: .strip("abc") no esborra la subcadena "abc", sinó qualsevol a, b o c que hi hagi als extrems. I strip no toca mai l'interior: " a b ".strip() deixa "a b".

  1. Majúscules i minúscules

Mètode Efecte "cartell FIRA del llibre" dóna
.upper() Tot majúscules CARTELL FIRA DEL LLIBRE
.lower() Tot minúscules cartell fira del llibre
.title() Primera lletra de cada paraula Cartell Fira Del Llibre
.capitalize() Primera lletra de la cadena, resta en minúscules Cartell fira del llibre
.casefold() Com lower, però més agressiu cartell fira del llibre

Els dos usos típics són diferents i convé no barrejar-los. Per comparar, es normalitza a minúscules (ningú no hauria de teclejar «Marta» amb la majúscula exacta perquè el programa ho accepti). Per mostrar, es tria la forma bonica:

resposta = "  MARTA "
print(resposta.strip().lower() in ("marta", "luis", "nuria"))   # True
print(resposta.strip().capitalize())                            # Marta

.casefold() és la versió rigorosa de .lower() per a comparacions internacionals: en alemany converteix la ß en ss, cosa que .lower() no fa. Per al català, totes dues es comporten igual; fes servir .casefold() si compares text que pot venir en qualsevol idioma.

  1. Cerca dins del text

Mètode Retorna Si no troba res
subcadena in text True / False False
.find(sub) Índex de la primera aparició -1
.rfind(sub) Índex de l'última aparició -1
.index(sub) Índex de la primera aparició ValueError
.count(sub) Nombre d'aparicions 0
.startswith(pre) True si comença així False
.endswith(suf) True si acaba així False
titol = "Cartell fira del llibre, fira de tardor"
print("fira" in titol)              # True   -> el mes llegible si nomes vols saber si hi es
print(titol.find("fira"))           # 8
print(titol.rfind("fira"))          # 25
print(titol.find("cartell"))        # -1     -> distingeix majuscules
print(titol.count("fira"))          # 2
print(titol.startswith("Cartell"))  # True
print(titol.endswith((".png", ".pdf")))     # False -> admet una tupla d'opcions

La diferència entre .find() i .index() és la mateixa que ja coneixes de les llistes: find retorna -1 quan no hi ha res, mentre que index llança ValueError. Fes servir find quan «no ser-hi» sigui un cas normal, i recorda que -1 és un índex vàlid en Python, així que mai no facis servir el resultat de find sense comprovar abans que no és -1.

  1. Comprovació del contingut

Aquests mètodes retornen True o False i serveixen per validar abans de convertir, tal com feies a 02-04.

Mètode True quan la cadena…
.isdigit() Només conté dígits (i no està buida)
.isalpha() Només conté lletres
.isalnum() Només conté lletres o dígits
.isspace() Només conté espais en blanc
print("365".isdigit(), "3.5".isdigit(), "-3".isdigit())     # True False False
print("Nuria".isalpha(), "Nuria 1".isalpha())               # True False
print("ALB01".isalnum(), "".isalnum())                      # True False
print("   ".isspace())                                      # True

Fixa't en els paranys: "3.5" no és isdigit (el punt no és un dígit), "-3" tampoc (el signe no ho és), i una cadena buida retorna False en tots ells. Per això if text.isdigit(): abans d'int(text) continua sent la guarda correcta per a enters positius, però no val per a decimals ni negatius. La manera robusta de gestionar aquests casos és try/except, i arriba a Depuració i gestió d'errors.

  1. Transformació i farciment

Mètode Què fa Exemple
.replace(vell, nou) Substitueix totes les aparicions "a-b-c".replace("-", " ") → "a b c"
.replace(v, n, compte) Només les primeres compte "a-b-c".replace("-", " ", 1) → "a b-c"
.zfill(n) Omple amb zeros per l'esquerra "7".zfill(3) → "007"
.center(n, c) Centra en n caràcters "ALB".center(9, "-") → "---ALB---"
.ljust(n, c) Alinea a l'esquerra "ALB".ljust(6, ".") → "ALB..."
.rjust(n, c) Alinea a la dreta "ALB".rjust(6, ".") → "...ALB"

zfill és la drecera clàssica per numerar: str(7).zfill(2) dóna "07", i així els codis surten 01, 02, 03… en comptes d'1, 2, 3. Els tres d'alineació fan el mateix que els especificadors :<, :^ i :> de les f-strings; pots fer servir qualsevol de les dues formes, encara que dins d'una f-string queda més net l'especificador.

  1. Partició i unió: el pont amb les llistes

Aquí hi ha la connexió directa amb la lliçó anterior, i és de les coses més útils del llenguatge.

split(separador) parteix una cadena i retorna una llista de trossos. Sense arguments, parteix per espais (agrupant els consecutius).

linia = "Cartell fira;luis;alta;3"
print(linia.split(";"))     # ['Cartell fira', 'luis', 'alta', '3']  -> 4 camps
print("  Cartell   fira ".split())      # ['Cartell', 'fira']
print(linia.split(";", 1))  # ['Cartell fira', 'luis;alta;3']  -> nomes el primer tall
Mètode Què fa
.split(sep) Parteix per sep d'esquerra a dreta; sense sep, per espais
.rsplit(sep, n) Igual, però compta els n talls des de la dreta
.splitlines() Parteix per salts de línia, sense deixar el \n

join fa el contrari: uneix els elements d'una llista en una sola cadena. Es crida sobre el separador, que és el que despista al principi:

equip = ["Marta", "Luis", "Nuria"]
print(", ".join(equip))     # Marta, Luis, Nuria
print("".join(equip))       # MartaLuisNuria
print(", ".join([str(d) for d in [3, 5, 2]]))   # 3, 5, 2

Es llegeix així: «agafa la llista i enganxa-la fent servir aquesta cadena com a cola». La tercera línia recorda que join només funciona amb llistes de cadenes: si hi ha números, cal convertir-los abans, normalment amb una comprensió de 05-01.

split i join són la parella que converteix text en dades i dades en text. Quan a 05-05 llegim un fitxer de tasques, cada línia es convertirà en camps amb split; i per escriure-la, els camps tornaran a ser una línia amb join.

  1. Multilínia, escapaments i cadenes crues

Per a un text de diverses línies hi ha les cometes triples, que respecten els salts tal com s'escriuen:

AJUDA = """TascaFacil - ajuda rapida
  1  Registrar una tasca
  2  Veure el llistat complet
  5  Sortir"""
print(AJUDA)

Dins d'una cadena normal, alguns caràcters s'escriuen amb una seqüència d'escapament, que comença per barra invertida:

Escapament Significa
\n Salt de línia
\t Tabulador
\\ Una barra invertida literal
\" i \' Cometa doble o simple dins de la cadena
print("Marta\tLuis\tNuria")             # separats per tabuladors
print("Ruta: C:\\alba\\tasques")        # Ruta: C:\alba\tasques
print('Va dir "molt be" i va marxar')   # cometes simples fora, dobles dins
print(r"C:\alba\tasques")               # C:\alba\tasques  -> cadena crua

L'última línia fa servir el prefix r de cadena crua (raw), que desactiva els escapaments i resulta comodíssim amb rutes de Windows. Compte amb el parany clàssic: "C:\tasques" no és el que sembla, perquè \t és un tabulador; amb r"C:\tasques" no hi ha sorpresa.

  1. f-strings, str() i Unicode

Les f-strings de 02-03 són la manera normal de construir text amb valors a dins, i els seus especificadors d'amplada són els que donen a TascaFàcil les seves taules alineades:

titol, responsable, dies = "Cartell fira", "Marta", 3
print(f"{titol:<24}{responsable:>10}{dies:>5}")     # ample fix per columna
print(f"Mitjana: {10 / 3:.2f}")                     # dos decimals: 3.33

str(valor) converteix qualsevol valor en la seva representació textual, i és imprescindible abans de concatenar amb + o de fer servir join. Recorda que "Dies: " + 3 dóna TypeError, mentre que "Dies: " + str(3) funciona i f"Dies: {3}" funciona sense conversió explícita.

Una idea sobre Unicode, que ja n'hi ha prou de moment. Cada caràcter d'una cadena de Python és un caràcter, no un byte: len("Solé") val 4 encara que la é porti accent, i "ç"[0] és "ç". Però quan aquest text surt del programa —a un fitxer, a la xarxa— cal codificar-lo en bytes, i aquí és on entra UTF-8, la codificació estàndard que sap representar accents, ñ, ç i pràcticament qualsevol alfabet. Si obres un fitxer amb la codificació equivocada veuràs coses com Solé. Per això a Desar dades en fitxers escriurem sempre encoding="utf-8".

  1. Comparar i ordenar textos

Les cadenes es comparen amb <, > i == caràcter a caràcter, segons el número que cada caràcter té assignat a la taula Unicode. I aquí apareix una sorpresa:

print("Zebra" < "abella")       # True
print("alta" < "baixa")         # True
print("Marta" == "marta")       # False

"Zebra" < "abella" és True perquè totes les majúscules van abans que totes les minúscules: la Z és el caràcter 90 i la a el 97. No és cap caprici: és l'ordre de la taula, no el del diccionari normatiu. La conseqüència pràctica és que sorted(["nuria", "Luis", "Marta"]) posa primer tot el que comenci per majúscula. La solució, que ja vas veure a 04-05 i 05-01, és ordenar per una clau normalitzada:

print(sorted(["ana", "Zoe"]))                   # ['Zoe', 'ana']  -> majuscules primer
print(sorted(["ana", "Zoe"], key=str.lower))    # ['ana', 'Zoe']  -> ordre esperat

El mateix val per comparar: normalitza els dos costats abans de comparar. entrada.strip().lower() == "marta" accepta " Marta ", "MARTA" i "marta". Els accents afegeixen un altre esglaó ("Angel" < "Àngel"), i el seu tractament correcte requereix biblioteques d'intercalació que queden fora d'aquest curs.

  1. Aplicació: títols i codis de tasca a Estudi Alba

Tres utilitats de text per a TascaFàcil, escrites amb el d'aquesta lliçó. L'estructura del programa no canvia encara —la tasca continua sent un grapat de variables fins a 05-03—, però aquestes funcions entraran a la secció de lògica.

def normalitzar_titol(text):
    """Retorna el titol sense espais sobrants ni dobles, amb la inicial en majuscula."""
    net = " ".join(text.split())            # treu extrems i espais repetits
    return net.capitalize()

def codi_tasca(client, titol, numero):
    """Retorna un codi tipus ALB-FOR-01 a partir del client i del titol."""
    sigles_client = client.strip().replace(" ", "")[:3].upper()
    inicial = titol.strip()[:1].upper()
    return f"ALB-{sigles_client}-{str(numero).zfill(2)}{inicial}"

def partir_linia(linia):
    """Parteix 'titol;responsable;prioritat;dies' i retorna els quatre camps."""
    camps = linia.strip().split(";")
    if len(camps) != 4:
        print("Linia mal formada:", linia)
        return None
    titol, responsable, prioritat, dies = camps
    return (normalitzar_titol(titol), responsable.strip().capitalize(),
            prioritat.strip().lower(), int(dies))

Provem-les:

print(normalitzar_titol("   cartell   FIRA del llibre "))       # Cartell fira del llibre
print(codi_tasca("Forn Sole", "Menu de temporada", 1))          # ALB-FOR-01M
print(partir_linia("Cartell fira;luis;alta;3"))     # ('Cartell fira', 'Luis', 'alta', 3)

Comentari del que és important:

  • " ".join(text.split()) és l'idioma clàssic per col·lapsar espais: split() sense arguments parteix per qualsevol bloc de blancs i descarta els buits; join els torna a unir amb exactament un espai. Resol de cop els espais dels extrems, els dobles i els tabuladors.
  • client.replace(" ", "")[:3].upper() construeix les sigles: treu els espais, agafa tres caràcters amb un tall i els passa a majúscules. Si el client tingués menys de tres lletres, el tall retorna el que hi hagi sense error.
  • str(numero).zfill(2) converteix primer a text (zfill és un mètode de cadena, no d'enter) i després omple amb zeros.
  • partir_linia retorna None si el nombre de camps no és l'esperat, aplicant la clàusula de guarda de 04-02, i normalitza cada camp amb les convencions del programa: títol capitalitzat, responsable capitalitzat, prioritat en minúscules i dies convertits a enter.

Aquesta funció partir_linia és, de fet, mitja lliçó 05-05: llegir un fitxer de tasques consistirà a aplicar-la a cada línia.

Errors Comuns i Consells

Oblidar que els mètodes no modifiquen. titol.upper() sense assignar no fa res visible; si el text no canvia, comprova que has escrit titol = titol.upper(). I titol[0] = "M" dóna TypeError, perquè la cadena és immutable: per canviar un caràcter cal reconstruir, "M" + titol[1:].

Fer servir el resultat de .find() sense comprovar-lo. Si no troba res retorna -1, i text[-1] és l'últim caràcter, així que el programa continuarà endavant amb una dada errònia en comptes de fallar.

Comparar sense normalitzar. entrada == "marta" falla amb " Marta ". Aplica .strip().lower() als dos costats, sempre al mateix lloc del programa.

Creure que .strip("hola") esborra la paraula «hola». Esborra qualsevol h, o, l o a dels extrems. Per treure una paraula concreta, .replace("hola", "").

join amb números. ", ".join([3, 5]) llança TypeError. Converteix abans: ", ".join([str(d) for d in dies]).

Consell: normalitza a l'entrada, formata a la sortida. Desa les dades ja netes (una sola forma canònica) i decideix la presentació en imprimir. Així mai no tindràs dues versions de «Cartell fira» convivint a la mateixa agenda.

Exercicis

Exercici 1: Predir la sortida

Digues què imprimeix cada línia i explica per què.

text = "  Menu Forn SOLE  "
print(f"[{text.strip().lower()}]")
print(text.find("Sole"), text.find("SOLE"))
print(text.strip().split())
print("-".join(text.split()))
print(text.replace(" ", ""))
print("SOLE" < "Sole", "SOLE".isalpha())

Exercici 2: Fitxa de text

Escriu resum_titol(titol) que rebi un títol i retorni una cadena d'una línia amb: el títol normalitzat (sense espais sobrants i capitalitzat), el seu nombre de caràcters, el seu nombre de paraules i les seves inicials en majúscules separades per punts. Per a " cartell fira del llibre " ha de retornar Cartell fira del llibre | 23 car. | 4 par. | C.F.D.L.

Exercici 3: Lector de línies de l'estudi

Donada aquesta llista de línies, escriu un programa que les parteixi, descarti les mal formades (que no tinguin quatre camps o els dies de les quals no siguin un enter) i imprimeixi una taula alineada amb títol, responsable capitalitzat, prioritat en majúscules i dies. Al final, imprimeix el total de dies de les línies vàlides.

linies = [
    "Cartell fira;luis;alta;3",
    "  menu forn sole ; MARTA ; mitjana ; 5 ",
    "Logotip Vidal;nuria;baixa",
    "Flyer estiu;marta;alta;dos",
]

Solucions

Solució 1.

[menu forn sole]
-1 12
['Menu', 'Forn', 'SOLE']
Menu-Forn-SOLE
MenuFornSOLE
True True

strip().lower() neteja i abaixa tot. find("Sole") dóna -1 perquè la cerca distingeix majúscules i al text hi diu SOLE; find("SOLE") sí que el troba, a l'índex 12 comptant els dos espais inicials. split() sense arguments ignora els espais sobrants. join enganxa els trossos amb guions. replace(" ", "") elimina tots els espais, també els interiors. I "SOLE" < "Sole" és True perquè la O majúscula (79) va abans que la o minúscula (111).

Solució 2.

def resum_titol(titol):
    """Retorna una linia amb el titol normalitzat i les seves estadistiques."""
    net = " ".join(titol.split()).capitalize()
    paraules = net.split()
    inicials = ".".join([p[0].upper() for p in paraules]) + "."
    return f"{net} | {len(net)} car. | {len(paraules)} par. | {inicials}"

print(resum_titol("  cartell  fira del llibre "))
# Cartell fira del llibre | 23 car. | 4 par. | C.F.D.L.

La clau és calcular net una sola vegada i mesurar sobre ell: si comptessis els caràcters de l'original obtindries 27 per culpa dels espais sobrants. Les inicials combinen una comprensió de 05-01 (p[0].upper() per a cada paraula) amb join, i el punt final s'afegeix a part perquè join només posa separadors entre elements.

Solució 3.

total = 0
print(f"{'TITOL':<24}{'RESP.':>10}{'PRIOR.':>10}{'DIES':>6}")
for linia in linies:
    camps = linia.strip().split(";")
    if len(camps) != 4 or not camps[3].strip().isdigit():
        print(f"Descartada: {linia.strip()}")
        continue
    titol = " ".join(camps[0].split()).capitalize()
    responsable = camps[1].strip().capitalize()
    prioritat = camps[2].strip().upper()
    dies = int(camps[3].strip())
    total += dies
    print(f"{titol:<24}{responsable:>10}{prioritat:>10}{dies:>6}")
print(f"{'TOTAL':<24}{total:>26}")

Surten dues files vàlides —«Cartell fira / Luis / ALTA / 3» i «Menu forn sole / Marta / MITJANA / 5», total 8 dies— i dos descarts: la tercera línia només té tres camps i la quarta porta "dos" on hi hauria d'anar un número. Dos detalls que valen per a qualsevol lector de dades. La comprovació len(camps) != 4 or not camps[3].strip().isdigit() reuneix les dues causes de descart en una sola guarda, i el continue de 03-03 evita imbricar la resta del cos dins d'un if. I cada camp es neteja amb .strip() abans de fer-lo servir, perquè la segona línia porta espais al voltant dels punts i comes: sense aquest strip, " 5 ".isdigit() donaria False i la línia bona es descartaria.

Conclusió

Una cadena és una seqüència immutable de caràcters: admet índexs, talls i recorreguts amb for igual que una llista, però no es pot modificar, així que tots els seus mètodes retornen una cadena nova i cal desar-la. Has recorregut les sis famílies de mètodes: neteja (strip, lstrip, rstrip), majúscules i minúscules (upper, lower, title, capitalize, casefold), cerca (in, find, rfind, index, count, startswith, endswith), comprovació (isdigit, isalpha, isalnum, isspace), transformació (replace, zfill, center, ljust, rjust) i partició i unió (split, rsplit, splitlines, join), que és el pont entre el text i les llistes. Saps escriure textos multilínia amb """, protegir-te dels escapaments amb r"", alinear columnes amb f-strings, i per què "Zebra" < "abella" obliga a comparar i ordenar sobre una versió en minúscules. I saps que els accents i la ç són un caràcter cadascun, però que en sortir a disc el text es codifica en bytes i per això UTF-8 importa.

A Estudi Alba ja pots normalitzar els títols perquè « CARTELL fira » i «Cartell Fira» siguin el mateix text, generar codis com ALB-FOR-01M i convertir una línia "Cartell fira;luis;alta;3" en quatre camps a punt per fer servir. El que continua sense resoldre's és el problema que 05-01 va deixar obert: aquests quatre camps continuen sent quatre variables soltes, o pitjor, quatre llistes paral·leles que cal mantenir sincronitzades a mà. La solució arriba a Diccionaris i conjunts: una estructura on cada dada es desa amb el seu nom —titol, responsable, prioritat, dies, completada— en lloc d'amb un número de posició. Allà TascaFàcil farà el salt a la v0.9 i una tasca passarà a ser, per fi, una sola cosa.

© Copyright 2026. Tots els drets reservats