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
- Els dos caps solts, en codi
- Conversió explícita:
int(),float(),str() - Quines conversions funcionen i quines fallen: el
ValueError - Conversió implícita, i per què text i número no es barregen
bool()i les regles de veracitat- Normalitzar el text:
.strip(),.lower()i.capitalize() - Comprovar abans de convertir:
.isdigit()i.isalpha() - Validar contra una llista de valors permesos
- Què és validar i per què es valida sempre
- Un
ifmínim, i el que encara ens falta - TascaFàcil: sanejar i validar l'entrada
- Errors comuns i consells
- Exercicis
- Conclusió
- 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) # TypeErrorCap solt 2: el text no es compara com l'escriu una persona.
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.
- Conversió explícita:
int(), float(), str()
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:
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 zeroint() trunca, no arrodoneix: elimina la part decimal sense mirar-la. Si vols arrodonir a l'enter més proper, fes servir round():
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:
Però no accepta la coma decimal catalana:
É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 diesDit 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:
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.
- Quines conversions funcionen i quines fallen: el
ValueError
ValueErrorConvertir no sempre és possible. Aquesta taula resumeix el que pots esperar:
| Conversió | Resultat | Funciona? |
|---|---|---|
int("3") |
3 |
Sí |
int(" 3 ") |
3 |
Sí — int() ignora els espais dels extrems |
int("3.0") |
No → ValueError |
|
int("tres") |
No → ValueError |
|
int("") |
No → ValueError |
|
int(3.9) |
3 |
Sí (trunca) |
int(True) |
1 |
Sí (True és 1, False és 0) |
float("3.5") |
3.5 |
Sí |
float("3") |
3.0 |
Sí |
float("3,5") |
No → ValueError |
|
float("abc") |
No → ValueError |
|
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 aint(), el text"3.0"conté un punt que no pertany a un enter. Si esperes que l'usuari pugui escriure decimals, converteix ambfloat()i trunca després:int(float("3.0"))dóna3.str()ibool()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.
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.
- 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.
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:
É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?
Perquè seria ambigu. "3" + 4 té dues interpretacions igual de raonables:
- Com a suma:
3 + 4→7. - 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()"]
bool() i les regles de veracitat
bool() i les regles de veracitatbool() 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 <- compteAixò 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) # FalseCom 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:
- Normalitzar el text:
.strip(), .lower() i .capitalize()
.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
Elimina espais, tabuladors i salts de línia al principi i al final, però no els de dins:
É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:
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") # TrueLlegeix-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 resAquest é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()) # NuriaPosa la primera lletra en majúscula i la resta en minúscula. Això últim s'oblida i de vegades sorprèn:
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.
- Comprovar abans de convertir:
.isdigit() i .isalpha()
.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()) # FalseServeix 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.
- 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.
- 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:
-
Robustesa. Un programa que s'atura amb
ValueErrorperquè 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». -
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. -
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.
- Un
if mínim, i el que encara ens falta
if mínim, i el que encara ens faltaFins 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'
ifacaba 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.
elsemarca 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.
- 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:
PRIORITATSiEQUIPsó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). Un0hauria 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" + 4Exercici 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:
- Netegi les dues entrades d'espais sobrants.
- Normalitzi el nom a la forma
Nom(inicial majúscula, resta minúscules). - Comprovi que el nom és a l'equip (Marta, Luis, Nuria); si no, avisi i assigni
"Marta". - Comprovi que les hores són un número enter entre 1 i 40; si no, avisi i assigni
8. - Calculi i mostri les jornades completes de 8 hores i les hores soltes (recorda
//i%de la lliçó 02-02). - 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) # TrueEls 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.
Fonaments de la Programació
Mòdul 1: Introducció a la Programació
- Què és la programació?
- Història de la programació
- Llenguatges de programació
- Entorns de desenvolupament
- Del problema a l'algorisme
Mòdul 2: Conceptes Bàsics
- Variables i tipus de dades
- Operadors i expressions
- Entrada i sortida de dades
- Conversió de tipus i validació de dades
Mòdul 3: Estructures de Control
Mòdul 4: Funcions i Procediments
- Definició i ús de funcions
- Paràmetres i retorn de valors
- Àmbit de variables
- Descompondre un programa en funcions
- Funcions com a valors: lambda i ordre superior
Mòdul 5: Estructures de Dades
- Llistes i arrays
- Cadenes de caràcters
- Diccionaris i conjunts
- Tuples i estructures imbricades
- Desar dades en fitxers: text, CSV i JSON
Mòdul 6: Algorismes Bàsics
Mòdul 7: Objectes i Organització del Codi
- De les dades als objectes: classes i instàncies
- Atributs, mètodes i constructor
- Col·leccions d'objectes
- Mòduls, paquets i importacions
Mòdul 8: Bones Pràctiques i Eines
- Documentació i comentaris
- Depuració i gestió d'errors
- Control de versions
- Proves automatitzades
- Estil, llegibilitat i refactorització
