Fins ara TascaFàcil ha estat un guió d'un sol acte: es llança, fa preguntes, imprimeix una fitxa i mor. Si la Marta s'equivoca en assignar la prioritat, no la pot corregir: ha de tornar a executar el programa i teclejar-ho tot de nou. Si vol tornar a veure la fitxa, el mateix. Un programa així es demostra, però no es fa servir.
El que separa un script d'una aplicació és un patró sorprenentment simple: el bucle principal. L'aplicació mostra el que sap fer, espera una ordre, l'executa i es torna a mostrar, una vegada i una altra, fins que l'usuari decideix marxar. És l'esquelet de gairebé tot el programari interactiu que has fet servir mai, des d'un caixer automàtic fins a un editor de text.
El millor és que ja tens totes les peces: while True i break de la lliçó anterior, la validació insistent de 03-02, el despatx amb if/elif o match de 03-01 i 03-03. Aquesta lliçó només les munta juntes, i amb elles tanca el mòdul.
Contingut
- Anatomia del bucle principal
while Trueambbreak, i la variant amb bandera- Despatxar l'opció triada
- Dissenyar un menú que s'entengui
- Validar l'opció llegida
- Confirmacions per a accions destructives
- TascaFàcil v0.6: l'aplicació completa
- Errors comuns i consells
- Exercicis
- Conclusió del mòdul
- Anatomia del bucle principal
Un menú interactiu repeteix sempre el mateix cicle de cinc passos.
flowchart TD
A["Mostrar el menu"] --> B["Llegir opcio"]
B --> C{"Opcio valida ?"}
C -->|False| B
C -->|True| D{"Opcio de sortir ?"}
D -->|True| F["Acomiadar-se i acabar"]
D -->|False| E["Executar accio"]
E --> G["Pausa"]
G --> A
Els cinc passos, amb la seva traducció a codi:
| Pas | Què fa | Amb què s'implementa |
|---|---|---|
| Mostrar | Pinta les opcions disponibles | Diversos print() |
| Llegir | Recull l'elecció de l'usuari | input() |
| Validar | Rebutja el que no és una opció | while de validació (03-02) |
| Executar | Fa la feina demanada | if/elif o match (03-01, 03-03) |
| Tornar | Torna al principi | El mateix bucle |
I una regla d'or: el bucle no ha de poder acabar per accident. L'única sortida és l'opció de sortir; un error de l'usuari no pot tancar l'aplicació, perquè en una aplicació real això significa perdre la feina.
while True amb break, i la variant amb bandera
while True amb break, i la variant amb banderaLa forma més directa és el bucle infinit controlat que vas veure a 03-03: while True: fa voltes indefinidament i l'opció de sortir executa un break.
while True:
print("1. Veure la tasca")
print("2. Sortir")
opcio = input("Opcio: ").strip()
if opcio == "1":
print("Aqui hi aniria la fitxa de la tasca.")
elif opcio == "2":
print("Fins despres.")
breakHi ha una segona forma, amb una bandera booleana que governa la condició: s'inicialitza en_execucio = True abans del bucle, la capçalera passa a ser while en_execucio: i l'opció de sortir fa en_execucio = False en lloc del break. Totes dues són correctes i veuràs les dues en codi professional.
while True + break |
Bandera en_execucio |
|
|---|---|---|
| Condició de sortida | Amagada al cos | Visible a la capçalera |
| Sortida immediata | Sí: talla a l'acte | No: acaba la volta actual |
| Diversos punts de sortida | Un break per lloc |
Posar la bandera a False |
| Llegibilitat | Molt directa | Més declarativa |
La diferència pràctica importa: amb break el bucle es talla en aquell mateix instant; amb la bandera, la resta del cos de la volta actual sí que s'executa abans de tornar a comprovar la condició. Si després del despatx hi ha una pausa o un missatge, amb la bandera es veuran també en sortir. Farem servir break a TascaFàcil, que és el més habitual.
- Despatxar l'opció triada
Despatxar és dirigir l'execució cap al bloc que correspon a l'opció triada. Amb if/elif és immediat: if opcio == "1": … elif opcio == "2": … i un else final per al que no es reconeix. Però com que estem comparant un valor contra alternatives concretes, aquest és exactament l'escenari per al qual es va dissenyar match:
match opcio:
case "1":
print("Registrar tasca")
case "2":
print("Veure fitxa")
case "3" | "c":
print("Canviar prioritat")
case "5" | "s" | "sortir":
print("Fins despres")
case _:
print("Opcio no reconeguda")Fixa't en l'avantatge del patró |: permet acceptar dreceres amb naturalitat, de manera que l'usuari pugui teclejar 5, s o sortir per al mateix. Amb if/elif caldria escriure opcio in ("5", "s", "sortir"), que també funciona però es llegeix pitjor quan hi ha moltes opcions.
Un avís important: encara que el match d'un menú es llegeixi molt bé, el break que surt del bucle no es pot substituir per res del match. break continua pertanyent al while, i funciona igual dins d'un case.
- Dissenyar un menú que s'entengui
El menú és la interfície del teu programa, i un menú mal presentat converteix una aplicació correcta en una aplicació inutilitzable. N'hi ha prou amb sis regles:
- Numera les opcions i fes servir números consecutius des d'1. Teclejar un número és més ràpid i menys propens a error que escriure una paraula.
- Descriu accions, no substantius. «Canviar la prioritat» diu què passarà; «Prioritat», no.
- L'opció de sortir, sempre visible i sempre l'última. L'usuari ha de poder marxar sense endevinar com.
- Separa visualment el menú del resultat amb línies de guions o iguals. Recorda el truc de la lliçó 02-03:
print("=" * AMPLE). - Pausa abans de repintar. Si el menú es torna a dibuixar immediatament, el resultat de l'acció desapareix de la vista abans que doni temps a llegir-lo. La solució clàssica és un
input("Prem Intro per continuar...")el valor del qual s'ignora: només serveix per esperar. - Repeteix l'interval al missatge:
Tria una opcio (1-5):orienta molt millor queOpcio:.
AMPLE = 46
print("=" * AMPLE)
print(f"{'TascaFacil v0.6 - Estudi Alba':^{AMPLE}}")
print("=" * AMPLE)
print(" 1. Registrar / substituir la tasca")
print(" 2. Veure la fitxa de la tasca")
print(" 3. Canviar la prioritat")
print(" 4. Marcar com a completada")
print(" 5. Sortir")
print("-" * AMPLE)
- Validar l'opció llegida
L'opció és una dada que ve de fora, així que se li aplica el mateix tracte que a qualsevol altra: el marc de 02-04 —presència, tipus, domini— i el bucle insistent de 03-02.
OPCIONS = ("1", "2", "3", "4", "5")
opcio = input("Tria una opcio (1-5): ").strip()
while opcio not in OPCIONS:
opcio = input("Opcio no valida. Tria 1-5: ").strip()Tres decisions de disseny que convé entendre:
- Comparem textos, no números.
OPCIONSconté"1"i no1, així que no cal convertir. Si l'usuari teclejahola, no hi haValueErrorque valgui: simplement no és al catàleg. Convertir ambint()sense comprovar abans trencaria el programa. - El
.strip()és imprescindible. Un espai davant del número converteix" 1"en una dada invàlida, i l'usuari no veuria per què. - Intro sense escriure res produeix la cadena buida, que tampoc no és a
OPCIONS, així que el bucle torna a preguntar sense necessitat de cap cas especial. Això sí: convé que el missatge de retret sigui clar, perquè un menú que respon en silenci sembla penjat.
Si a més vols acceptar dreceres com s per sortir, n'hi ha prou de normalitzar amb .lower() i ampliar el catàleg: OPCIONS = ("1", "2", "3", "4", "5", "s").
- Confirmacions per a accions destructives
Una acció és destructiva si perd informació que l'usuari no pot recuperar: substituir la tasca registrada, esborrar-la, sortir de l'aplicació sense desar. Davant d'aquestes, un programa educat pregunta.
confirmar = input("Segur que vols substituir la tasca? (s/n): ").strip().lower()
if confirmar == "s":
print("Substituint...")
else:
print("Operacio cancellada.")El detall important és a l'else: comparem amb "s" i qualsevol altra cosa cancel·la. És deliberat. Davant del dubte —un dit maldestre, un Intro accidental, una x sense voler—, l'opció segura és no destruir res; si féssim if confirmar != "n":, qualsevol tecla equivocada executaria l'acció irreversible. I no n'abusis: si el programa demana confirmació per a tot, l'usuari aprèn a teclejar s sense llegir i la confirmació deixa de protegir. Confirma només allò que de debò no té marxa enrere.
- TascaFàcil v0.6: l'aplicació completa
Ja hi som. TascaFàcil deixa de ser un guió i es converteix en una aplicació amb cinc opcions sobre l'única tasca que sap gestionar. Fixa't en una variable nova i decisiva: hi_ha_tasca, la bandera que distingeix «encara no s'ha registrat res» de «hi ha una tasca per consultar o modificar».
# tascafacil.py - Estudi Alba
# Versio 0.6: aplicacio amb menu principal
AMPLE = 46
PRIORITATS = ("alta", "mitjana", "baixa")
EQUIP = ("Marta", "Luis", "Nuria")
OPCIONS = ("1", "2", "3", "4", "5")
# --- Estat de l'aplicacio: les dades de LA tasca ---
hi_ha_tasca = False
titol = ""
responsable = ""
prioritat = ""
dies = 0
completada = False
while True:
print("\n" + "=" * AMPLE)
print(f"{'TascaFacil v0.6 - Estudi Alba':^{AMPLE}}")
print("=" * AMPLE)
print(" 1. Registrar / substituir la tasca")
print(" 2. Veure la fitxa de la tasca")
print(" 3. Canviar la prioritat")
print(" 4. Marcar com a completada")
print(" 5. Sortir")
print("-" * AMPLE)
opcio = input("Tria una opcio (1-5): ").strip()
while opcio not in OPCIONS:
opcio = input("Opcio no valida. Tria 1-5: ").strip()
if opcio == "1":
if hi_ha_tasca:
confirmar = input(f"Ja existeix '{titol}'. Substituir-la? (s/n): ").strip().lower()
if confirmar != "s":
print("Operacio cancellada.")
continue
titol = input("Titol : ").strip()
while titol == "":
titol = input("Buit. Titol : ").strip()
responsable = input("Responsable : ").strip().capitalize()
while responsable not in EQUIP:
responsable = input("No es de l'equip. Responsable: ").strip().capitalize()
prioritat = input("Prioritat : ").strip().lower()
while prioritat not in PRIORITATS:
prioritat = input("No valida. Prioritat: ").strip().lower()
dies_text = input("Dies (1-365) : ").strip()
while not dies_text.isdigit() or not 1 <= int(dies_text) <= 365:
dies_text = input("Enter 1-365. Dies: ").strip()
dies = int(dies_text)
completada = False
hi_ha_tasca = True
print("Tasca registrada correctament.")
elif opcio == "2":
if not hi_ha_tasca:
print("Encara no hi ha cap tasca registrada.")
else:
estat = "Completada" if completada else "Pendent"
print("-" * AMPLE)
print(f"{'Titol':<14}{titol:>{AMPLE - 14}}")
print(f"{'Responsable':<14}{f'{responsable} ({prioritat})':>{AMPLE - 14}}")
print(f"{'Dies / estat':<14}{f'{dies} / {estat}':>{AMPLE - 14}}")
elif opcio == "3":
if not hi_ha_tasca:
print("Encara no hi ha cap tasca registrada.")
else:
nova = input("Nova prioritat (alta/mitjana/baixa): ").strip().lower()
while nova not in PRIORITATS:
nova = input("No valida. Prioritat: ").strip().lower()
print(f"Prioritat de '{titol}': {prioritat} -> {nova}")
prioritat = nova
elif opcio == "4":
if not hi_ha_tasca:
print("Encara no hi ha cap tasca registrada.")
elif completada:
print("La tasca ja estava completada.")
else:
confirmar = input(f"Marcar '{titol}' com a completada? (s/n): ").strip().lower()
if confirmar == "s":
completada = True
print("Tasca completada. Bona feina.")
else:
print("Operacio cancellada.")
elif opcio == "5":
confirmar = input("Segur que vols sortir? (s/n): ").strip().lower()
if confirmar == "s":
print("Fins despres. TascaFacil es tanca.")
break
input("\nPrem Intro per tornar al menu...")Quatre detalls del codi mereixen comentari:
- L'estat viu fora del bucle. Les variables
titol,prioritat,hi_ha_tasca… es declaren abans delwhilei sobreviuen a totes les voltes. Si fossin a dins, cada volta les reiniciaria i l'aplicació oblidaria la tasca tot just pintar el menú. És el mateix principi que l'acumulador de 03-02. hi_ha_tascaprotegeix les opcions 2, 3 i 4. Sense aquesta comprovació, triar «Veure la fitxa» abans de registrar res mostraria una fitxa buida i absurda. Una aplicació seriosa mai no ofereix una dada que no té.- El
continuede l'opció 1 cancel·la la substitució i torna al menú saltant-se la resta de la volta, inclosa la pausa final. És exactament elcontinuede 03-03 aplicat al bucle principal. - Sortir també es confirma, perquè en la versió actual tancar el programa perd la tasca. I només
"s"surt: qualsevol altra tecla continua a l'aplicació.
Una sessió de treball típica de la Marta:
Tria una opcio (1-5): 9 Opcio no valida. Tria 1-5: 1 Titol : Cartell fira del llibre Responsable : nuria Prioritat : urgentissima No valida. Prioritat: alta Dies (1-365) : 4 Tasca registrada correctament. Prem Intro per tornar al menu...
Després, l'opció 3 canvia la prioritat a mitjana, la 2 mostra la fitxa actualitzada i la 5 demana confirmació abans de tancar. La mateixa execució del programa fa tot això, que és justament el que no podíem fer en cap versió anterior.
Errors Comuns i Consells
Declarar l'estat dins del bucle. L'error número u dels menús: l'aplicació «oblida» les dades a cada volta. Tot el que hagi de sobreviure va abans del while.
Oblidar el break a l'opció de sortir. El menú es torna inescapable i només queda Ctrl+C. Comprova-ho sempre el primer de tot.
Convertir l'opció amb int() sense validar. Si l'usuari tecleja una lletra, ValueError i programa mort. Treballa amb l'opció com a text i compara-la contra un catàleg.
No pausar abans de repintar. El resultat apareix i desapareix en el mateix parpelleig; un input("Prem Intro...") ho resol. I no ofereixis opcions impossibles, com consultar una tasca que encara no existeix: una bandera d'estat com hi_ha_tasca i un missatge clar ho eviten.
Confirmar amb != "n". Qualsevol tecla despistada executa l'acció destructiva. Confirma sempre amb == "s" i deixa que tota la resta cancel·li.
Consell: desa el nombre d'opcions en una constant. Amb OPCIONS a dalt del fitxer, afegir una opció és tocar dos llocs (la constant i el menú), no remenar per tot el programa.
Consell: prova el teu menú com un usuari hostil. Tecleja lletres, espais, Intro a seques, números fora d'interval, i tria l'opció 2 tot just arrencar. Cada cosa que trenqui el programa és una validació que falta.
Exercicis
Exercici 1: Detectar les fallades
Aquest menú té tres defectes greus. Troba'ls i explica què li passa a l'usuari en cada cas.
while True:
comptador_usos = 0
print("1. Veure 2. Sortir")
opcio = int(input("Opcio: "))
if opcio == 1:
comptador_usos += 1
print(f"Consultes: {comptador_usos}")
elif opcio == 2:
print("Adeu")Exercici 2: Menú de l'agenda de l'equip
Escriu agenda_equip.py, una aplicació amb bucle principal i menú de quatre opcions que gestioni una sola nota d'agenda: 1) escriure/substituir la nota (text no buit i dia de la setmana entre 1 i 5), 2) veure la nota, 3) esborrar la nota (amb confirmació), 4) sortir. Fes servir una bandera hi_ha_nota, valida l'opció amb un while, despatxa amb match i pausa abans de repintar.
Exercici 3: Comptador d'operacions
Amplia TascaFàcil v0.6 perquè porti el compte de quantes vegades s'ha fet servir cada opció durant la sessió i mostri un resum en sortir, amb el patró comptador de 03-02. Indica on s'han de declarar els comptadors i per què.
Solucions
Solució 1.
| Defecte | Què li passa a l'usuari |
|---|---|
comptador_usos = 0 és dins del bucle |
Es reinicia a cada volta: el comptador sempre mostra 1 |
int(input(...)) sense validar |
Si tecleja una lletra o prem Intro, el programa mor amb ValueError |
L'opció 2 no té break |
Imprimeix «Adeu» i torna al menú: és impossible sortir |
Tots tres tenen la mateixa arrel: el bucle es va escriure pensant només en l'usuari que fa el que està previst. La versió corregida treu el comptador fora del while, llegeix l'opció com a text validat contra un catàleg i afegeix el break.
Solució 2.
# agenda_equip.py - Estudi Alba
OPCIONS = ("1", "2", "3", "4")
DIES = ("1", "2", "3", "4", "5")
hi_ha_nota = False
nota = ""
dia = ""
while True:
print(" 1. Escriure / substituir la nota 2. Veure la nota")
print(" 3. Esborrar la nota 4. Sortir")
opcio = input("Tria una opcio (1-4): ").strip()
while opcio not in OPCIONS:
opcio = input("Opcio no valida. Tria 1-4: ").strip()
match opcio:
case "1":
nota = input("Text de la nota: ").strip()
while nota == "":
nota = input("No pot estar buida. Nota: ").strip()
dia = input("Dia laborable (1-5): ").strip()
while dia not in DIES:
dia = input("Ha de ser d'1 a 5. Dia: ").strip()
hi_ha_nota = True
print("Nota desada.")
case "2":
if hi_ha_nota:
print(f"Dia {dia}: {nota}")
else:
print("No hi ha cap nota escrita.")
case "3":
if not hi_ha_nota:
print("No hi ha res per esborrar.")
else:
confirmar = input(f"Esborrar '{nota}'? (s/n): ").strip().lower()
if confirmar == "s":
hi_ha_nota = False
nota = ""
print("Nota esborrada.")
else:
print("Operacio cancellada.")
case "4":
print("Fins despres.")
break
input("\nPrem Intro per continuar...")Observa que el break de case "4" funciona amb normalitat: pertany al while, no al match. I que esborrar no consisteix només a posar hi_ha_nota = False: convé netejar també nota, perquè cap camí del programa no pugui mostrar una dada que l'usuari creu eliminada.
Solució 3.
Els comptadors —un per opció— s'han de declarar abans del while, al costat de la resta de l'estat, exactament per la mateixa raó que hi_ha_tasca: si fossin a dins, es reiniciarien a cada volta i sempre valdrien 1. S'incrementen al principi del bloc de cada opció, i el resum s'imprimeix just abans del break:
usos_registrar = 0
usos_veure = 0
usos_prioritat = 0
usos_completar = 0
while True:
# ... menu i validacio de la opcio ...
if opcio == "1":
usos_registrar += 1
# ... registre de la tasca ...
elif opcio == "5":
confirmar = input("Segur que vols sortir? (s/n): ").strip().lower()
if confirmar == "s":
print(f"Resum: {usos_registrar} registres, {usos_veure} consultes,")
print(f"{usos_prioritat} canvis de prioritat, {usos_completar} completades.")
breakI aquí apunta un problema que ja no és de bucles: quatre variables gairebé idèntiques per comptar quatre coses gairebé idèntiques. És el mateix símptoma que veurem a la conclusió, i la seva solució és al mòdul 5.
Conclusió del mòdul
TascaFàcil ha arribat a la versió 0.6 i ja és una aplicació de veritat. Mostra un menú, espera ordres, valida el que rep, executa l'acció demanada, avisa quan alguna cosa no es pot fer, confirma allò irreversible i només es tanca quan l'usuari ho diu. La Marta pot registrar la tasca, consultar-la, canviar-li la prioritat i marcar-la com a completada sense tornar a llançar el programa.
I amb això es tanca el mòdul 3. Saps decidir amb if, elif i else, amb condicions compostes, imbricació i clàusules de guarda (03-01). Saps repetir amb while i for, amb range() i les seves tres formes, i amb els quatre patrons clàssics —comptador, acumulador, màxim i bandera— (03-02). Saps controlar el flux amb precisió mitjançant break, continue, la clàusula else dels bucles, els bucles imbricats i match (03-03). I saps muntar tot això en un bucle d'aplicació amb menú, validació, despatx, pausa i confirmacions (03-04). Amb aquestes tres estructures —seqüència, decisió i repetició— pots expressar qualsevol algorisme que existeixi: és un resultat clàssic de la informàtica, i ja el tens sencer.
Però mira la versió 0.6 amb ulls crítics, perquè els seus dos límits són el mapa del que ve. El primer: el codi s'ha tornat llarg i repetitiu. Gairebé cent línies en un sol bloc, amb if not hi_ha_tasca: escrit tres vegades, quatre bucles de validació que s'assemblen com dues gotes d'aigua i un menú que creixerà cada vegada que hi afegim una funció. Tot això demana a crits poder donar nom a un tros de codi i reutilitzar-lo: escriure una vegada demanar_prioritat() i cridar-la des d'on calgui. Això són les funcions, i són el mòdul 4 sencer.
El segon: només hi cap una tasca. Tota l'aplicació gira al voltant d'un únic joc de variables —titol, responsable, prioritat, dies—, i registrar una tasca nova significa destruir l'anterior. Per això l'opció 1 demana confirmació. Però l'estudi no té una tasca: en té desenes, i la Marta necessita veure-les totes, filtrar-les per responsable i ordenar-les per prioritat. Per a això calen estructures capaces de desar molts valors sota un sol nom: les llistes, que arriben a Llistes i arrays, i amb elles el for que recorre una col·lecció de tasques de veritat.
Dit d'una altra manera: saps construir la maquinària d'un programa, i ara toca aprendre a organitzar-la i a donar-li dades amb les quals valgui la pena treballar. Ens veiem al mòdul 4, amb les funcions.
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ó
