Aquesta lliçó tanca el mòdul 2 lligant dos caps que arrossegàvem des de fa temps.

El primer es va descobrir amb llapis i paper, a la prova d'escriptori de la lliçó Del problema a l'algorisme: quan la Marta teclejava ALTA en majúscules, l'algorisme rebutjava una prioritat perfectament correcta, perquè per a un ordinador "ALTA" i "alta" són dos textos diferents. Vam prometre resoldre-ho aquí.

El segon va aparèixer a la lliçó anterior: input() retorna sempre un str, així que tan bon punt TascaFàcil vulgui demanar els dies estimats o les hores dedicades i fer-hi comptes, s'estavellarà amb un TypeError. Per això aquestes dades continuen escrites a mà al codi.

Tots dos són manifestacions del mateix principi, que és la idea de fons d'aquesta lliçó: tota dada que ve de fora del programa és sospitosa fins que es demostri el contrari. Pot tenir el tipus equivocat, sobrar-li espais, venir en majúscules, estar buida o ser directament un disbarat. La feina de convertir-la al tipus correcte s'anomena conversió; la de comprovar que té sentit, validació. Un programa que no fa les dues coses no és un programa acabat: és un prototip que funciona mentre ningú no el faci servir de debò.

Aprendràs a convertir amb int(), float(), str() i bool(), a interpretar els errors ValueError i TypeError, a sanejar text amb .strip(), .lower() i .capitalize(), i a comprovar dades abans de fer-les servir amb .isdigit(), .isalpha() i l'operador in.

Contingut

  1. Els dos caps solts, en codi
  2. Conversió explícita: int(), float(), str()
  3. Quines conversions funcionen i quines fallen: el ValueError
  4. Conversió implícita, i per què text i número no es barregen
  5. bool() i les regles de veracitat
  6. Normalitzar el text: .strip(), .lower() i .capitalize()
  7. Comprovar abans de convertir: .isdigit() i .isalpha()
  8. Validar contra una llista de valors permesos
  9. Què és validar i per què es valida sempre
  10. Un if mínim, i el que encara ens falta
  11. TascaFàcil: sanejar i validar l'entrada
  12. Errors comuns i consells
  13. Exercicis
  14. Conclusió

  1. Els dos caps solts, en codi

Comencem veient els dos problemes amb tota la seva cruesa.

Cap solt 1: el que arriba d'input() és text.

dies = input("Dies estimats: ")      # l'usuari tecleja 3

print(type(dies))        # <class 'str'>
print(dies + 1)          # TypeError
TypeError: can only concatenate str (not "int") to str

Cap solt 2: el text no es compara com l'escriu una persona.

prioritat = input("Prioritat: ")     # l'usuari tecleja ALTA

print(prioritat == "alta")           # False

I hi ha més variants del mateix problema, totes versemblants:

El que teclega l'usuari prioritat == "alta" Per què
alta True Coincideix exactament
ALTA False Majúscules diferents
Alta False La primera lletra difereix
alta (amb espai final) False L'espai és un caràcter més
alta (espai davant) False Ídem
urgent False No és una prioritat vàlida
(Intro sense escriure res) False És la cadena buida ""

De les set files, només la primera és el que volíem, l'última és realment un error de l'usuari, i les cinc intermèdies són entrades raonables que un programa decent hauria d'acceptar o rebutjar amb criteri, no per accident.

Les cinc primeres s'arreglen convertint i normalitzant; les dues últimes, validant. Anem-hi.

  1. Conversió explícita: int(), float(), str()

Python porta funcions que transformen un valor d'un tipus a un altre. Es diuen igual que el tipus destinació i es fan servir posant el valor entre parèntesis.

Funció Converteix a Exemple Resultat
int(x) Enter int("3") 3
float(x) Decimal float("3.5") 3.5
str(x) Text str(3.5) "3.5"
bool(x) Booleà bool("") False

Això s'anomena conversió explícita o casting: ets tu qui la demana, en un punt concret del codi.

int(): de text a enter

Resol el primer cap solt de cop:

dies_text = input("Dies estimats: ")       # l'usuari tecleja 3

dies = int(dies_text)

print(type(dies))            # <class 'int'>
print(dies + 1)              # 4    <- ara si que es aritmetica
print(dies * 2)              # 6    <- ja no repeteix el text

És habitual escriure-ho tot en una línia, embolcallant l'input() directament:

dies = int(input("Dies estimats: "))

Es llegeix de dins cap enfora: primer s'executa input(), que retorna text; aquest text es passa a int(), que retorna un enter; i l'enter s'assigna a dies. És compacte i molt comú, però té un inconvenient que veurem a l'apartat 7: si l'usuari teclega alguna cosa que no és un número, el programa s'atura allà mateix.

int() també converteix decimals a enters, i aquí cal fixar-se en un detall:

print(int(3.9))      # 3    <- NO arrodoneix a 4
print(int(3.1))      # 3
print(int(-3.9))     # -3   <- talla cap a zero

int() trunca, no arrodoneix: elimina la part decimal sense mirar-la. Si vols arrodonir a l'enter més proper, fes servir round():

print(round(3.9))    # 4
print(round(3.1))    # 3

Confondre int() amb round() produeix errors silenciosos en càlculs d'imports i percentatges. Apunta-t'ho.

float(): a decimal

hores = float(input("Hores dedicades: "))     # l'usuari tecleja 7.5

print(hores)         # 7.5
print(type(hores))   # <class 'float'>

float() accepta també text sense decimals, i el converteix afegint-hi .0:

print(float("3"))    # 3.0

Però no accepta la coma decimal catalana:

print(float("3,5"))
ValueError: could not convert string to float: '3,5'

És un problema real amb usuaris d'aquí, que teclegen la coma amb tota naturalitat. Un programa acurat reemplaça la coma per un punt abans de convertir. Això es fa amb el mètode .replace(), que pertany a Cadenes de caràcters; ho esmento perquè sàpigues que hi ha solució.

str(): a text

És la conversió inversa: converteix qualsevol valor en la seva representació textual.

dies = 7
completada = False
data = None

print(str(dies))          # 7      (pero ara es un str)
print(str(completada))    # False
print(str(data))          # None

print(type(str(dies)))    # <class 'str'>

El seu ús clàssic és concatenar un número amb text fent servir +, cosa que sense conversió donava TypeError:

dies = 7

print("Queden " + dies + " dies")          # TypeError
print("Queden " + str(dies) + " dies")     # Queden 7 dies

Dit això: en codi nou gairebé mai no necessitaràs str() per a això, perquè les f-strings de la lliçó anterior fan la conversió soles i queden molt millor:

print(f"Queden {dies} dies")               # Queden 7 dies

str() continua sent útil en altres contextos —el vam fer servir a la fitxa de TascaFàcil per alinear un booleà com si fos text—, però no com a forma habitual de compondre missatges.

  1. Quines conversions funcionen i quines fallen: el ValueError

Convertir no sempre és possible. Aquesta taula resumeix el que pots esperar:

Conversió Resultat Funciona?
int("3") 3
int(" 3 ") 3 Sí — int() ignora els espais dels extrems
int("3.0") NoValueError
int("tres") NoValueError
int("") NoValueError
int(3.9) 3 Sí (trunca)
int(True) 1 Sí (True és 1, False és 0)
float("3.5") 3.5
float("3") 3.0
float("3,5") NoValueError
float("abc") NoValueError
str(qualsevol cosa) El seu text Sempre funciona
bool(qualsevol cosa) True o False Sempre funciona

Hi ha tres files que mereixen comentari:

  • int(" 3 ") funciona. int() és tolerant amb els espais dels extrems. No ho és amb res més.
  • int("3.0") falla. Sorprèn, perquè 3.0 és un número. Però per a int(), el text "3.0" conté un punt que no pertany a un enter. Si esperes que l'usuari pugui escriure decimals, converteix amb float() i trunca després: int(float("3.0")) dóna 3.
  • str() i bool() mai no fallen. Qualsevol valor té representació textual i qualsevol valor és vertader o fals. Això els fa segurs, però també silenciosos: no t'avisaran d'una dada absurda.

Llegir un ValueError

Quan la conversió no es pot fer, Python llança un ValueError: el tipus era el correcte (li has passat un text a int(), que és el que espera), però el valor concret no serveix.

dies = int("tres")
Traceback (most recent call last):
  File "tascafacil.py", line 4, in <module>
    dies = int("tres")
           ^^^^^^^^^^^
ValueError: invalid literal for int() with base 10: 'tres'

Un missatge d'error de Python es llegeix de baix a dalt, i conté més informació de la que sembla:

Part Què et diu
ValueError El tipus d'error: el valor no és convertible
invalid literal for int() Estaves intentant convertir a enter
with base 10 En base decimal (la normal)
'tres' El valor exacte que ha fallat
File "tascafacil.py", line 4 El fitxer i la línia on ha passat

Aquesta última fila és la més valuosa i la que més gent ignora: l'error et diu on és el problema. No llegeixis només l'última línia; mira també el número de línia.

I no confonguis els dos errors que apareixen en convertir:

Error Significa Exemple
ValueError El valor no serveix per a aquella conversió int("tres")
TypeError El tipus no admet aquella operació "3" + 4

Quan passa un ValueError, el programa s'atura. Tot el que hi hagués després no s'executa. Hi ha dues maneres d'evitar-ho: comprovar abans de convertir (el que farem a l'apartat 7) o capturar l'error quan passa, amb try / except, que s'estudia a Depuració i gestió d'errors.

  1. Conversió implícita, i per què text i número no es barregen

Hi ha conversions que Python fa sol, sense que les demanis. S'anomenen implícites, i passen únicament entre tipus numèrics.

resultat = 3 + 1.5

print(resultat)         # 4.5
print(type(resultat))   # <class 'float'>

Python ha convertit el 3 (un int) a 3.0 (un float) per poder sumar-lo amb 1.5. La regla és senzilla: quan es barregen int i float, el resultat és sempre float, perquè és el tipus més «ampli», el que pot representar els valors de l'altre sense perdre informació.

print(type(5 + 2))       # <class 'int'>
print(type(5 + 2.0))     # <class 'float'>
print(type(5 / 2))       # <class 'float'>   <- recorda: / sempre dona float
print(type(5 // 2))      # <class 'int'>

Els booleans també hi participen, perquè internament True val 1 i False val 0:

print(True + True)       # 2
print(False + 10)        # 10

És una curiositat més que una tècnica; no escriguis codi que en depengui.

Per què Python no converteix text i número

I ara la pregunta que es fa tothom: si Python converteix int a float sense demanar-l'hi, per què no converteix "3" a 3?

print("3" + 4)
TypeError: can only concatenate str (not "int") to str

Perquè seria ambigu. "3" + 4 té dues interpretacions igual de raonables:

  • Com a suma: 3 + 47.
  • Com a concatenació: "3" + "4""34".

Python no endevina. És una decisió de disseny deliberada, i molt encertada: prefereix aturar-se i avisar-te a fer alguna cosa diferent del que volies. Altres llenguatges sí que endevinen, i aquesta comoditat els surt cara. En JavaScript, "3" + 4 dóna "34" i "3" - 4 dóna -1: la mateixa parella de valors es comporta com a text amb un operador i com a número amb un altre, cosa que és una font inesgotable d'errors difícils de trobar.

La moralitat pràctica: quan vegis un TypeError que esmenta str i int, tens una dada amb el tipus equivocat. Gairebé sempre és un input() sense convertir.

flowchart TD
    A["Barrejo dos valors<br/>en una operacio"] --> B{"Tots dos son<br/>numerics?"}
    B -->|"int i float"| C["Python converteix sol<br/>Resultat: float"]
    B -->|"str i numero"| D["TypeError<br/>Converteix tu amb int(), float() o str()"]

  1. bool() i les regles de veracitat

bool() converteix qualsevol valor a True o False, i mai no falla. Però les seves regles cal conèixer-les, perquè no són òbvies.

En Python, uns pocs valors es consideren falsos; tots els altres són vertaders.

Valor bool(valor) Comentari
0 False El zero enter
0.0 False El zero decimal
"" False Cadena buida
None False Absència de valor
False False Òbviament
" " True Un espai no és buit: té un caràcter
"0" True És un text d'un caràcter, no el número zero
"False" True És un text! I no està buit
-1 True Qualsevol número diferent de zero
3.14 True Ídem

Les tres files perilloses són " ", "0" i "False". Les tres semblen que haurien de donar False i les tres donen True, perquè són textos no buits. bool() sobre un text només pregunta una cosa: té almenys un caràcter?

print(bool(""))          # False
print(bool(" "))         # True    <- compte
print(bool("False"))     # True    <- compte
print(bool("0"))         # True    <- compte

Això té una aplicació directa i importantíssima per a nosaltres: detectar si l'usuari ha premut Intro sense escriure res.

titol = input("Titol: ")             # l'usuari prem Intro directament

hi_ha_titol = bool(titol)
print(hi_ha_titol)                   # False

Com que input() retorna "" quan no s'escriu res, i "" és fals, bool(titol) ens diu si hi ha alguna cosa. A la pràctica s'escriu més curt: el mateix valor del text ja serveix com a condició, sense embolcallar-lo en bool(). És el que Python anomena valor de veritat d'un objecte.

Però atenció al parany que se'n deriva, de la taula: si l'usuari teclega només espais, titol val " ", que és vertader. Un títol de tres espais passaria el filtre. La solució és treure els espais abans de comprovar, i d'això tracta l'apartat següent.

Un últim avís: no facis servir mai bool() per interpretar una resposta de l'usuari.

resposta = input("Completada? (True/False): ")    # l'usuari tecleja False

print(bool(resposta))      # True    <- NO es el que esperaves

"False" és un text no buit, així que bool() dóna True. Per interpretar un sí/no cal comparar el text, no convertir-lo:

completada = resposta.strip().lower() == "si"

  1. Normalitzar el text: .strip(), .lower() i .capitalize()

Normalitzar és transformar una dada a una forma canònica abans de comparar-la o desar-la, de manera que les diferències irrellevants —espais sobrants, majúscules— deixin d'importar.

Python ofereix molts mètodes de cadena; els veuràs tots a Cadenes de caràcters. Aquí ens quedem amb els tres imprescindibles per sanejar entrades.

Es criden amb un punt darrere de la variable de text: variable.metode().

.strip(): treure espais dels extrems

entrada = "  alta  "

print(entrada)              # '  alta  ' (amb espais)
print(entrada.strip())      # 'alta'

Elimina espais, tabuladors i salts de línia al principi i al final, però no els de dins:

print("  Forn  Sole  ".strip())          # 'Forn  Sole'

És la primera operació que ha de patir qualsevol text que vingui d'un teclat. Els espais sobrants són invisibles a la pantalla i trenquen totes les comparacions.

.lower(): passar-ho tot a minúscules

Aquí hi ha, per fi, la solució al cap solt de la lliçó 01-05:

prioritat = "ALTA"

print(prioritat == "alta")             # False
print(prioritat.lower() == "alta")     # True

També existeix .upper(), que fa el contrari. La tècnica s'anomena comparació insensible a majúscules, i consisteix a portar els dos costats a la mateixa caixa abans de comparar. Com que el literal "alta" ja és en minúscules, n'hi ha prou de baixar l'altre.

El normal és encadenar les dues operacions. Els mètodes s'apliquen d'esquerra a dreta:

prioritat = input("Prioritat: ")        # l'usuari tecleja "  ALTA  "

prioritat = prioritat.strip().lower()

print(prioritat)                        # 'alta'
print(prioritat == "alta")              # True

Llegeix-ho així: al text original se li treuen els espais, i al resultat se li abaixen les majúscules. I fixa't en una cosa important de la segona línia: el resultat es reassigna a la mateixa variable. Si escrivissis només prioritat.strip().lower() sense el prioritat = al davant, el resultat es calcularia i es llençaria, perquè els mètodes de cadena no modifiquen el text original: en retornen un de nou. És la immutabilitat de les cadenes que vas veure a la lliçó 02-01, ara amb conseqüències pràctiques.

prioritat = "  ALTA  "
prioritat.strip().lower()      # es calcula i es perd
print(prioritat)               # '  ALTA  '  <- no ha canviat res

Aquest és un dels errors més freqüents en qui comença. Recorda-ho: els mètodes de cadena retornen, no modifiquen.

.capitalize(): primera lletra en majúscula

Útil per a noms propis, que és exactament el cas del responsable d'una tasca:

print("nuria".capitalize())      # Nuria
print("NURIA".capitalize())      # Nuria
print("nUrIa".capitalize())      # Nuria

Posa la primera lletra en majúscula i la resta en minúscula. Això últim s'oblida i de vegades sorprèn:

print("anna maria".capitalize()) # Anna maria   <- nomes la primera paraula

Per posar en majúscula la inicial de cada paraula existeix .title(), amb les seves pròpies limitacions. Com que l'equip d'Estudi Alba són tres noms d'una sola paraula, .capitalize() ens n'hi ha prou.

Mètode Què fa " nUrIa "
.strip() Treu espais dels extrems "nUrIa"
.lower() Tot a minúscules " nuria "
.upper() Tot a majúscules " NURIA "
.capitalize() Primera lletra majúscula, resta minúscula " nuria "
.strip().capitalize() Encadenats "Nuria"

Fixa't en la penúltima fila: .capitalize() sobre un text que comença per espai no fa res visible, perquè la «primera lletra» és un espai. El .strip() va sempre primer.

  1. Comprovar abans de convertir: .isdigit() i .isalpha()

Ja sabem que int("tres") atura el programa. La manera d'evitar-ho sense recórrer encara a la gestió d'errors és preguntar abans si la conversió és possible.

.isdigit() respon a: aquest text està compost només per dígits? Retorna un booleà.

print("3".isdigit())         # True
print("300".isdigit())       # True
print("tres".isdigit())      # False
print("3.5".isdigit())       # False   <- el punt no es un digit
print("-5".isdigit())        # False   <- el signe tampoc
print("".isdigit())          # False   <- cadena buida
print(" 3".isdigit())        # False   <- l'espai tampoc

És una comprovació estricta, i aquesta estrictesa és una virtut: si .isdigit() retorna True, int() funcionarà amb seguretat. Però tingues presents els seus tres límits: no admet decimals, no admet negatius i no tolera espais. Per això el .strip() ha d'anar abans:

entrada = input("Dies estimats: ")      # l'usuari tecleja "  5  "

entrada = entrada.strip()
print(entrada.isdigit())                # True

.isalpha() és el seu equivalent per a lletres: està compost només per lletres?

print("Luis".isalpha())          # True
print("Nuria3".isalpha())        # False
print("Luis Gomez".isalpha())    # False   <- l'espai no es una lletra
print("".isalpha())              # False

Serveix per detectar que algú ha escrit un número on anava un nom. Compte amb la tercera línia: un nom compost conté un espai i no passa el filtre, així que .isalpha() és adequat per a noms d'una sola paraula, com els de l'equip d'Estudi Alba, però no per a noms i cognoms.

Mètode Retorna True quan... Casos que rebutja
.isdigit() Només hi ha dígits i n'hi ha almenys un "", "3.5", "-5", "3 "
.isalpha() Només hi ha lletres i n'hi ha almenys una "", "Luis1", "Luis Gomez"
.isspace() Només hi ha espais en blanc "", qualsevol text amb contingut

El patró d'ús segur queda així:

1. Llegir amb input()
2. Netejar amb .strip()  (i .lower() si es una opcio d'un conjunt)
3. Comprovar amb .isdigit() / in (...) / longitud
4. Nomes si la comprovacio passa: convertir amb int() o float()

Aquest ordre no és negociable: netejar, comprovar, convertir.

  1. Validar contra una llista de valors permesos

Quan una dada només pot prendre uns pocs valors —com la prioritat de TascaFàcil, que segons el requisit R2 només admet alta, mitjana i baixa—, la comprovació s'escriu amb l'operador in que vas aprendre a la lliçó 02-02:

prioritat = input("Prioritat (alta/mitjana/baixa): ")
prioritat = prioritat.strip().lower()

prioritat_valida = prioritat in ("alta", "mitjana", "baixa")
print(prioritat_valida)

Amb aquesta normalització prèvia, totes aquestes entrades s'accepten correctament: alta, ALTA, Alta, alta , aLtA. I aquestes es rebutgen, com ha de ser: urgent, altissima, `` (buit), 1.

El mateix per al responsable, que segons R3 ha de ser una de les tres persones de l'estudi:

responsable = input("Responsable (Marta/Luis/Nuria): ")
responsable = responsable.strip().capitalize()

responsable_valid = responsable in ("Marta", "Luis", "Nuria")
print(responsable_valid)

Fixa't en la diferència de normalització: per a la prioritat fem servir .lower(), perquè els valors canònics són en minúscula; per al responsable fem servir .capitalize(), perquè són noms propis i els volem desar ben escrits. La normalització ha de portar la dada a la forma en què la desaràs, no a una forma qualsevol.

Avantatges d'aquesta forma davant d'encadenar comparacions amb or:

  • Es llegeix com la regla de negoci: «la prioritat ha de ser entre aquestes tres».
  • Afegir un valor permès és afegir un element, no una altra comparació.
  • No es pot caure en el parany de prioritat == "alta" or "mitjana" que vam veure a 02-02.

  1. Què és validar i per què es valida sempre

Recapitulem amb calma, perquè aquest apartat és el cor conceptual de la lliçó.

Validar és comprovar que una dada compleix les condicions necessàries per poder-se fer servir, abans de fer-la servir. No és una comprovació de tipus: és una comprovació de sentit.

Una dada pot fallar en quatre nivells diferents, i convé distingir-los:

Nivell Pregunta Exemple de fallada Eina
Presència Hi ha alguna cosa? Títol buit .strip() i comprovar si queda alguna cosa
Tipus Es pot convertir? Dies = "tres" .isdigit(), després int()
Domini És entre els valors permesos? Prioritat = "urgent" in ("alta", "mitjana", "baixa")
Coherència Té sentit? Dies estimats = -5 o 9000 Comparacions: 1 <= dies <= 365

Aquest quart nivell s'oblida sempre i és on es colen les dades més absurdes. "-5" no passaria .isdigit(), però "0" sí, i una tasca de zero dies estimats no significa res. Convertir bé no n'hi ha prou: cal preguntar-se si el valor té sentit en el negoci.

I per què es valida tot el que ve de fora, sempre, sense excepció? Per tres raons de pes creixent:

  1. Robustesa. Un programa que s'atura amb ValueError perquè algú va teclejar una lletra de més és un programa que ningú no voldrà fer servir. La Marta no hauria d'haver de trucar a ningú perquè el programa «ha petat».

  2. Integritat de les dades. Si TascaFàcil accepta la prioritat "urgentissima", aquesta tasca no apareixerà mai en filtrar per prioritat alta, mitjana o baixa. El programa no fallarà: mentirà, que és molt pitjor. Un error visible s'arregla; una dada corrupta es propaga durant mesos.

  3. Seguretat. En programes connectats a bases de dades o a internet, les dades no validades són la porta d'entrada dels atacs més comuns del món. Queda fora de l'abast d'aquest curs, però l'hàbit que estàs construint ara és exactament el que evita aquests problemes.

I la regla d'or, que convé aprendre's literalment:

No confiïs mai en les dades que vénen de fora del programa. Ni del teclat, ni d'un fitxer, ni de la xarxa. Ni tan sols si qui les teclega ets tu.

Aquesta última cueta no és retòrica. El programador és el pitjor provador del seu propi programa, perquè teclega sempre el que el programa espera. La Marta no.

  1. Un if mínim, i el que encara ens falta

Fins ara hem calculat booleans (prioritat_valida, hi_ha_titol) i els hem imprès. Però validar serveix de poc si el programa no fa alguna cosa diferent segons el resultat.

Per a això hi ha l'if, que s'estudia a fons a la propera lliçó, Condicionals. Aquí el fem servir en la seva forma més simple, només perquè la validació tingui efecte visible:

prioritat = input("Prioritat (alta/mitjana/baixa): ")
prioritat = prioritat.strip().lower()

if prioritat in ("alta", "mitjana", "baixa"):
    print(f"Prioritat acceptada: {prioritat}")
else:
    print("Prioritat no valida. S'assigna 'mitjana' per defecte.")
    prioritat = "mitjana"

print(f"Prioritat final: {prioritat}")

Tres apunts sobre la sintaxi, sense estendre'ns perquè no és el tema d'aquesta lliçó:

  • La línia de l'if acaba en dos punts (:).
  • El que va a sota està indentat (4 espais), i aquesta indentació és la que li diu a Python quines instruccions pertanyen a cada branca. En Python la indentació no és estètica: és sintaxi.
  • else marca el camí alternatiu, el que es pren quan la condició és falsa.

Amb això, la validació ja no només informa: actua. Rebutja el valor incorrecte i en posa un per defecte.

Tot i així, queden dues limitacions que aquest mòdul no pot resoldre:

No podem tornar a preguntar. El natural seria insistir fins que l'usuari escrigui alguna cosa vàlida, tal com vam dissenyar al pseudocodi de la lliçó 01-05 (aquell MENTRE prioritat_valida = FALS). Repetir requereix un bucle, i els bucles arriben a Bucles; l'aplicació completa a un menú de TascaFàcil, a Menús interactius. Per ara, l'estratègia és assignar un valor per defecte i avisar.

No podem capturar l'error d'una conversió. Si l'usuari teclega "tres" on va un número, la nostra defensa és comprovar abans amb .isdigit(). L'altra via —deixar que la conversió falli i atrapar el ValueError amb try / except— és més potent i més habitual en codi professional, i s'estudia a Depuració i gestió d'errors.

Estratègia Com funciona On s'estudia
Comprovar abans (.isdigit()) S'evita l'error preguntant primer Aquí
Valor per defecte (if / else) S'accepta una alternativa raonable Aquí, a fons a 03-01
Tornar a preguntar S'insisteix fins a obtenir una dada vàlida 03-02 i 03-04
Capturar l'error (try / except) Es deixa fallar i es gestiona la caiguda 08-02

Les quatre són vàlides i es combinen. Acabaràs el curs fent servir les quatre.

  1. TascaFàcil: sanejar i validar l'entrada

Versió 0.5 de tascafacil.py. Reprenem la 0.4 de la lliçó anterior i hi afegim l'après: normalitzem els textos, convertim els números que ara sí que podem demanar per teclat, i validem els quatre nivells de l'apartat 9.

# tascafacil.py - Estudi Alba
# Versio 0.5: entrada sanejada, convertida i validada

NOM_APP = "TascaFacil"
VERSIO = "0.5"
ESTUDI = "Estudi Alba"
AMPLE = 46
HORES_PER_JORNADA = 8

PRIORITATS = ("alta", "mitjana", "baixa")
EQUIP = ("Marta", "Luis", "Nuria")
DIES_PER_DEFECTE = 5

# --- Capcalera ---
print("=" * AMPLE)
print(f"{NOM_APP + ' v' + VERSIO:^{AMPLE}}")
print(f"{'Registre de tasca - ' + ESTUDI:^{AMPLE}}")
print("=" * AMPLE)
print()

# ============================================================
# ENTRADA + SANEJAT + VALIDACIO
# ============================================================

# --- 1. Titol: presencia ---
titol = input("Titol de la tasca             : ")
titol = titol.strip()

avis_titol = ""
if titol == "":
    titol = "(sense titol)"
    avis_titol = "AVIS: el titol estava buit."

# --- 2. Responsable: domini ---
responsable = input("Responsable (Marta/Luis/Nuria): ")
responsable = responsable.strip().capitalize()

avis_responsable = ""
if responsable not in EQUIP:
    avis_responsable = f"AVIS: '{responsable}' no es de l'equip. S'assigna a Marta."
    responsable = "Marta"

# --- 3. Prioritat: domini ---
prioritat = input("Prioritat (alta/mitjana/baixa): ")
prioritat = prioritat.strip().lower()

avis_prioritat = ""
if prioritat not in PRIORITATS:
    avis_prioritat = f"AVIS: '{prioritat}' no es una prioritat. S'assigna 'mitjana'."
    prioritat = "mitjana"

# --- 4. Dies estimats: tipus + coherencia ---
dies_text = input("Dies estimats                 : ")
dies_text = dies_text.strip()

avis_dies = ""
if dies_text.isdigit():
    dies_estimats = int(dies_text)
    if dies_estimats < 1 or dies_estimats > 365:
        avis_dies = f"AVIS: {dies_estimats} dies no es raonable. S'assignen {DIES_PER_DEFECTE}."
        dies_estimats = DIES_PER_DEFECTE
else:
    avis_dies = f"AVIS: '{dies_text}' no es un numero. S'assignen {DIES_PER_DEFECTE} dies."
    dies_estimats = DIES_PER_DEFECTE

# ============================================================
# PROCES
# ============================================================

hores_estimades = dies_estimats * HORES_PER_JORNADA
completada = False
es_urgent = prioritat == "alta" and not completada

# ============================================================
# SORTIDA
# ============================================================

print()
print("=" * AMPLE)
print(f"{'FITXA DE TASCA':^{AMPLE}}")
print("=" * AMPLE)
print(f"{'Titol':<16}{titol:>{AMPLE - 16}}")
print(f"{'Responsable':<16}{responsable:>{AMPLE - 16}}")
print(f"{'Prioritat':<16}{prioritat:>{AMPLE - 16}}")
print(f"{'Dies estimats':<16}{dies_estimats:>{AMPLE - 16}}")
print(f"{'Hores estimades':<16}{hores_estimades:>{AMPLE - 16}}")
print(f"{'Urgent':<16}{str(es_urgent):>{AMPLE - 16}}")
print("=" * AMPLE)

# --- Avisos de validacio ---
print()
print(avis_titol)
print(avis_responsable)
print(avis_prioritat)
print(avis_dies)

Una execució amb entrades «brutes» però raonables, de les que la Marta escriuria un dilluns amb presses:

==============================================
               TascaFacil v0.5
       Registre de tasca - Estudi Alba
==============================================

Titol de la tasca             :   Manual de marca Sole
Responsable (Marta/Luis/Nuria): LUIS
Prioritat (alta/mitjana/baixa):   ALTA
Dies estimats                 :  4

==============================================
                FITXA DE TASCA
==============================================
Titol                     Manual de marca Sole
Responsable                               Luis
Prioritat                                 alta
Dies estimats                                4
Hores estimades                             32
Urgent                                    True
==============================================

Les quatre entrades venien amb espais o en majúscules, i totes quatre s'han acceptat i desat en la seva forma canònica. A la versió 0.4, aquesta mateixa execució hauria produït Urgent: False, perquè " ALTA " no era "alta". El cap solt de la lliçó 01-05 queda lligat.

I ara una execució amb entrades realment errònies:

Titol de la tasca             :
Responsable (Marta/Luis/Nuria): Pere
Prioritat (alta/mitjana/baixa): urgentissima
Dies estimats                 : quatre

==============================================
                FITXA DE TASCA
==============================================
Titol                            (sense titol)
Responsable                              Marta
Prioritat                              mitjana
Dies estimats                                5
Hores estimades                             40
Urgent                                   False
==============================================

AVIS: el titol estava buit.
AVIS: 'Pere' no es de l'equip. S'assigna a Marta.
AVIS: 'urgentissima' no es una prioritat. S'assigna 'mitjana'.
AVIS: 'quatre' no es un numero. S'assignen 5 dies.

El programa no s'ha aturat en cap moment. Abans, int("quatre") hauria llançat un ValueError i l'execució hauria mort allà, sense fitxa i sense explicació. Ara cada dada incorrecta es detecta, se substitueix per alguna cosa raonable i s'informa del que ha passat.

Repassem les decisions de disseny, perquè són tan importants com el codi:

  • PRIORITATS i EQUIP són constants en majúscules. Les llistes de valors permesos no haurien d'estar escrites dins de la comprovació: posades a dalt, es veuen d'un cop d'ull i es canvien en un sol lloc.
  • L'ordre és sempre el mateix: llegir → .strip() → normalitzar → comprovar → convertir. No converteixis mai abans de comprovar.
  • Cada dada fa servir la normalització que li correspon: .lower() per a la prioritat (valor de catàleg en minúscules), .capitalize() per al responsable (nom propi), només .strip() per al títol (és text lliure; abaixar-lo a minúscules el faria malbé).
  • Els avisos es desen en variables i s'imprimeixen al final, perquè la fitxa surti neta i els problemes quedin agrupats. Quan no hi ha error, la variable val "" i s'imprimeix una línia buida.
  • Els dies es validen en dos nivells: primer que siguin un número (.isdigit()), després que el número tingui sentit (1 <= dies <= 365). Un 0 hauria passat el primer filtre i no el segon.

El que continua sent millorable

Sigues honest amb aquesta versió, perquè el mòdul 3 comença justament aquí:

Limitació Es resol a
Davant d'una dada incorrecta es posa un valor per defecte en lloc de tornar a preguntar Bucles
Els quatre blocs de validació repeteixen la mateixa estructura Definició i ús de funcions
Només es pot registrar una tasca per execució Menús interactius i Llistes
En tancar el programa, la tasca es perd Desar dades en fitxers
Els avisos buits imprimeixen línies en blanc Condicionals

Errors Comuns i Consells

Oblidar convertir el que retorna input(). És l'error número u del mòdul. Si has de calcular, converteix. Si el TypeError esmenta str i int, mira els teus input().

Convertir abans de comprovar. int(input(...)) és còmode i fràgil: qualsevol lletra tomba el programa. L'ordre segur és llegir, netejar, comprovar i només llavors convertir.

Creure que int() arrodoneix. int(3.9) és 3. Per arrodonir, round().

Esperar que int("3.0") funcioni. No funciona: el punt no pertany a un enter. Fes servir int(float("3.0")) si necessites admetre aquesta forma.

Oblidar que els mètodes de cadena no modifiquen. titol.strip() sense assignar no fa res. Ha de ser titol = titol.strip().

Normalitzar només un costat de la comparació. prioritat.lower() == "Alta" continua sent False sempre. Si abaixes un costat, l'altre ha de ser ja en minúscules.

Aplicar .capitalize() abans de .strip(). " nuria".capitalize() no canvia res visible, perquè la primera «lletra» és un espai. El .strip() va primer.

Confiar en bool() per interpretar respostes. bool("False") és True, i bool("0") també. Per a sí/no, compara el text normalitzat.

Comprovar el buit sense netejar abans. titol == "" no detecta un títol de tres espais. titol.strip() == "" sí.

Fer servir .isdigit() amb negatius o decimals. "-5" i "3.5" retornen False. Si la teva dada admet decimals o signe, .isdigit() no és la comprovació adequada.

Validar només el tipus i oblidar la coherència. "0" i "99999" són dígits perfectament vàlids, i cap dels dos no és un nombre de dies raonable. Pregunta't sempre si el valor té sentit en el problema, no només si és convertible.

Consell: normalitza en el moment de llegir. Deixa dada = input(...).strip() just a la lectura, i a partir d'aquí treballa sempre amb la dada neta. Si la neteja queda dispersa pel programa, tard o d'hora hi haurà un camí que se la saltarà.

Consell: posa les opcions vàlides en constants. PRIORITATS = ("alta", "mitjana", "baixa") a dalt del fitxer. La regla de negoci queda visible i es canvia en un sol lloc.

Consell: prova el teu programa com si el volguessis trencar. Prem Intro sense escriure. Teclega espais. Escriu en majúscules. Posa una lletra on va un número. Posa un número negatiu. Cada cosa que trenqui el programa és una validació que falta.

Exercicis

Exercici 1: Predir el resultat

Indica el resultat —o l'error, amb el seu nom— de cada expressió, sense executar res.

a)  int("42")
b)  int("42.0")
c)  int(42.9)
d)  round(42.9)
e)  float("7")
f)  float("7,5")
g)  str(True) + " valor"
h)  bool("")
i)  bool(" ")
j)  bool("0")
k)  "  Alta  ".strip().lower()
l)  "  alta  ".lower().strip() == "alta"
m)  "LUIS".capitalize()
n)  "  luis".capitalize()
o)  "12".isdigit()
p)  "-12".isdigit()
q)  "3 dies".isdigit()
r)  "Nuria" in ("Marta", "Luis", "Nuria")
s)  "nuria" in ("Marta", "Luis", "Nuria")
t)  "3" + 4

Exercici 2: Sanejar una entrada d'hores

Escriu un programa registrar_hores.py que demani per teclat el nom d'una persona de l'equip i les hores dedicades a una tasca (un número enter), i que:

  1. Netegi les dues entrades d'espais sobrants.
  2. Normalitzi el nom a la forma Nom (inicial majúscula, resta minúscules).
  3. Comprovi que el nom és a l'equip (Marta, Luis, Nuria); si no, avisi i assigni "Marta".
  4. Comprovi que les hores són un número enter entre 1 i 40; si no, avisi i assigni 8.
  5. Calculi i mostri les jornades completes de 8 hores i les hores soltes (recorda // i % de la lliçó 02-02).
  6. Mostri una fitxa alineada amb f-strings.

Prova el teu programa amb aquestes quatre entrades i comprova que cap no l'atura: NURIA / 12, després pere / 8, després Luis / vuit, i per últim Luis / 100.

Exercici 3: Validar els quatre nivells

Per a cadascuna d'aquestes dades de TascaFàcil, indica en quin nivell de validació falla (presència, tipus, domini o coherència), quina eina d'aquesta lliçó ho detecta i què hauria de fer el programa. Després escriu el fragment de codi que ho comprova.

Dada Valor introduït
a Títol: " "
b Prioritat: "URGENT"
c Dies estimats: "quinze"
d Dies estimats: "0"
e Responsable: "nuria"
f Hores dedicades: "50" sobre 8 hores estimades

Solucions

Solució 1.

Expressió Resultat
a int("42") 42
b int("42.0") ValueError: el punt no pertany a un enter
c int(42.9) 42 (trunca, no arrodoneix)
d round(42.9) 43
e float("7") 7.0
f float("7,5") ValueError: la coma no és separador decimal
g str(True) + " valor" "True valor"
h bool("") False
i bool(" ") True — un espai és un caràcter
j bool("0") True — és un text no buit
k " Alta ".strip().lower() "alta"
l " alta ".lower().strip() == "alta" True — aquí l'ordre és indiferent, però .strip() primer és l'hàbit correcte
m "LUIS".capitalize() "Luis"
n " luis".capitalize() " luis" — la primera «lletra» és un espai, no canvia res
o "12".isdigit() True
p "-12".isdigit() False — el signe no és un dígit
q "3 dies".isdigit() False
r "Nuria" in ("Marta", "Luis", "Nuria") True
s "nuria" in ("Marta", "Luis", "Nuria") False — distingeix majúscules; falta .capitalize()
t "3" + 4 TypeError

Les files n i s són les que més es fallen, i totes dues apunten al mateix: normalitzar malament és tan perillós com no normalitzar, perquè dóna falsa sensació de seguretat.

Solució 2.

# registrar_hores.py - Estudi Alba

AMPLE = 40
EQUIP = ("Marta", "Luis", "Nuria")
HORES_PER_JORNADA = 8
HORES_PER_DEFECTE = 8

print("=" * AMPLE)
print(f"{'HORES DEDICADES':^{AMPLE}}")
print("=" * AMPLE)
print()

# --- Nom: sanejat i validacio de domini ---
nom = input("Persona (Marta/Luis/Nuria): ")
nom = nom.strip().capitalize()

avis_nom = ""
if nom not in EQUIP:
    avis_nom = f"AVIS: '{nom}' no es de l'equip. S'assigna a Marta."
    nom = "Marta"

# --- Hores: validacio de tipus i de coherencia ---
hores_text = input("Hores dedicades (1-40)    : ")
hores_text = hores_text.strip()

avis_hores = ""
if hores_text.isdigit():
    hores = int(hores_text)
    if hores < 1 or hores > 40:
        avis_hores = f"AVIS: {hores} h es fora del rang 1-40. S'assignen {HORES_PER_DEFECTE}."
        hores = HORES_PER_DEFECTE
else:
    avis_hores = f"AVIS: '{hores_text}' no es un numero. S'assignen {HORES_PER_DEFECTE} h."
    hores = HORES_PER_DEFECTE

# --- Proces ---
jornades = hores // HORES_PER_JORNADA
hores_soltes = hores % HORES_PER_JORNADA

# --- Sortida ---
print()
print("-" * AMPLE)
print(f"{'Persona':<18}{nom:>{AMPLE - 18}}")
print(f"{'Hores dedicades':<18}{hores:>{AMPLE - 18}}")
print(f"{'Jornades completes':<18}{jornades:>{AMPLE - 18}}")
print(f"{'Hores soltes':<18}{hores_soltes:>{AMPLE - 18}}")
print("-" * AMPLE)
print(avis_nom)
print(avis_hores)

Comportament amb les quatre entrades demanades:

Entrada Nom final Hores finals Jornades Soltes Avís
NURIA / 12 Nuria 12 1 4 Cap
pere / 8 Marta 8 1 0 Pere no és de l'equip
Luis / vuit Luis 8 1 0 'vuit' no és un número
Luis / 100 Luis 8 1 0 100 h fora del rang

Cap de les quatre no atura el programa. Fixa't en la primera fila: " NURIA " es converteix en "Nuria" gràcies a .strip().capitalize(), i per això passa la validació de domini. Sense aquest sanejat, la Nuria —una persona real de l'equip— hauria estat rebutjada.

Solució 3.

Dada Nivell Eina Què fer
a Títol " " Presència titol.strip() == "" Rebutjar i demanar de nou, o posar "(sense titol)"
b Prioritat "URGENT" Domini .strip().lower() i després in PRIORITATS Rebutjar i assignar "mitjana" per defecte
c Dies "quinze" Tipus .isdigit() No convertir; avisar i fer servir el valor per defecte
d Dies "0" Coherència Comparació dies >= 1 Convertible i numèric, però sense sentit: rebutjar
e Responsable "nuria" Cap, després de normalitzar .strip().capitalize() Acceptar: és vàlid, només estava mal escrit
f Hores "50" sobre 8 estimades Coherència Comparació entre dues dades Avisar de desviació greu del pressupost

Codi de comprovació:

PRIORITATS = ("alta", "mitjana", "baixa")
EQUIP = ("Marta", "Luis", "Nuria")

# a) Presencia
titol = "   ".strip()
titol_ok = titol != ""
print(titol_ok)           # False

# b) Domini
prioritat = "URGENT".strip().lower()
prioritat_ok = prioritat in PRIORITATS
print(prioritat_ok)       # False

# c) Tipus
dies_text = "quinze".strip()
dies_ok_tipus = dies_text.isdigit()
print(dies_ok_tipus)      # False

# d) Coherencia
dies_text = "0".strip()
dies_ok_tipus = dies_text.isdigit()
print(dies_ok_tipus)      # True   <- passa el filtre de tipus
dies = int(dies_text)
dies_ok_rang = 1 <= dies <= 365
print(dies_ok_rang)       # False  <- pero no el de coherencia

# e) Domini, despres de normalitzar
responsable = "nuria".strip().capitalize()
responsable_ok = responsable in EQUIP
print(responsable)        # Nuria
print(responsable_ok)     # True

# f) Coherencia entre dues dades
hores_dedicades = int("50".strip())
hores_estimades = 8
desviacio_greu = hores_dedicades > hores_estimades * 2
print(desviacio_greu)     # True

Els casos d i e són els que ensenyen de debò. El d demostra que passar la validació de tipus no n'hi ha prou: "0" és un dígit perfectament convertible i una tasca de zero dies no significa res. I l'e demostra el contrari: una dada que semblava invàlida ("nuria" no és a EQUIP) resulta ser perfectament correcta tan bon punt es normalitza. Validar sense normalitzar abans rebutja dades bones; normalitzar sense validar després accepta dades dolentes. Calen les dues coses, i en aquest ordre.

Conclusió

Amb aquesta lliçó es tanca el mòdul 2, i es tanquen també els dos caps solts que el van obrir.

Saps convertir amb int(), float() i str(), i en coneixes els límits: int() trunca en lloc d'arrodonir, int("3.0") no funciona, float("3,5") tampoc. Saps llegir un ValueError —que et diu el valor exacte i la línia on va fallar— i distingir-lo del TypeError, que apareix quan el tipus no admet l'operació. Entens que Python converteixi int a float implícitament però es negui a barrejar text i número, i per què aquesta negativa és una virtut del llenguatge i no un caprici.

Coneixes les regles de veracitat de bool() i els seus tres paranys (" ", "0" i "False" són vertaders). Normalitzes el text amb .strip(), .lower() i .capitalize(), recordant que els mètodes de cadena retornen un text nou i no modifiquen l'original. Comproves abans de convertir amb .isdigit() i .isalpha(), i valides contra un catàleg de valors permesos amb in. I tens un marc mental per no oblidar-te de res: presència, tipus, domini i coherència, en aquest ordre.

Sobretot, has interioritzat el principi que sosté tot l'anterior: res del que ve de fora del programa és de fiar. Ni del teclat, ni d'un fitxer, ni de qui l'escriu, encara que siguis tu.

TascaFàcil és a la seva versió 0.5 i ha deixat de ser fràgil. La Marta pot teclejar ALTA , LUIS o Manual de marca Sole i el programa ho entén; pot escriure urgentissima, Pere o quatre i el programa no s'atura: avisa, corregeix i continua. La comparació que fallava a la prova d'escriptori de la lliçó 01-05 —aquell "ALTA" que no era "alta"— avui funciona.

Repassa el que has aconseguit en aquest mòdul: les dades d'una tasca viuen en variables amb el tipus adequat (02-01), es combinen en expressions que calculen terminis, avanços i urgències (02-02), entren per teclat i surten en una fitxa alineada amb f-strings (02-03), i arriben sanejades i validades (02-04). Això és un programa que es fa servir, no un programa que es llegeix.

I tanmateix ho has notat a cada apartat d'aquesta lliçó: el programa pren decisions a mitges. Posa valors per defecte perquè no sap tornar a preguntar. Registra una sola tasca i acaba. Els if que hem fet servir eren mínims i manllevats de la lliçó següent. Tot això són estructures de control: les instruccions que permeten a un programa triar camins i repetir accions, i que converteixen una seqüència lineal en una cosa que reacciona.

És justament el que comença al mòdul 3, amb Condicionals, on aquell if que hem fet servir de puntetes s'explica a fons —if, elif, else, condicions imbricades, indentació— i TascaFàcil passarà per fi d'avisar dels problemes a decidir què fer-ne.

© Copyright 2026. Tots els drets reservats