Tancàvem el mòdul 3 amb una frase: «saps construir la maquinària d'un programa, i ara toca aprendre a organitzar-la». Aquest és aquell moment. TascaFàcil v0.6 funciona —la Marta pot registrar una tasca, consultar-la, canviar-li la prioritat i completar-la sense tornar a llançar el programa—, però el fitxer tascafacil.py s'ha convertit en un mur de gairebé cent línies seguides on if not hi_ha_tasca: apareix tres vegades i quatre bucles de validació es repeteixen gairebé paraula per paraula.

Aquest mur no és un problema estètic, sinó pràctic: quan alguna cosa falla, no saps on mirar; quan vols canviar el missatge d'error de la prioritat, has de recordar canviar-lo als dos llocs on està escrit; i quan vols afegir una opció al menú, has d'obrir-te pas entre el codi de les cinc anteriors.

L'eina que resol tot això es diu funció. És, probablement, la idea més rendible de tot el curs: la que converteix un guió inmanejable en un programa que es pot llegir, provar i ampliar per parts. En aquesta lliçó aprendràs què és una funció, com es defineix, com es crida i què passa exactament quan la crides.

Contingut

  1. El problema: codi repetit i sense nom
  2. Què és una funció
  3. Sintaxi: def, cos indentat i crida
  4. Definir i cridar no són el mateix
  5. Ordre de definició i execució: el NameError
  6. El flux d'una crida, pas a pas
  7. Procediments i funcions que calculen: None
  8. Docstring i convencions de nom
  9. TascaFàcil: les tres primeres funcions
  10. Errors comuns i consells
  11. Exercicis
  12. Conclusió

  1. El problema: codi repetit i sense nom

Mira aquests dos fragments, extrets tal qual de TascaFàcil v0.6. Un és a l'opció 1 i l'altre a l'opció 3:

# A l'opcio 1 (registrar tasca)
prioritat = input("Prioritat     : ").strip().lower()
while prioritat not in PRIORITATS:
    prioritat = input("No valida. Prioritat: ").strip().lower()

# A l'opcio 3 (canviar prioritat)
nova = input("Nova prioritat (alta/mitjana/baixa): ").strip().lower()
while nova not in PRIORITATS:
    nova = input("No valida. Prioritat: ").strip().lower()

Són el mateix algorisme escrit dues vegades, i això comporta tres conseqüències desagradables:

  • Manteniment doble. Si demà l'estudi afegeix la prioritat critica, cal tocar els dos llocs. I el dia que només en toquis un, tindràs un programa que es comporta de manera diferent segons per on hi entris: un error especialment difícil de trobar.
  • Soroll. En llegir l'opció 1 no hi veus «es demana la prioritat»: hi veus tres línies de mecànica amb input, strip, lower i un while. El què queda enterrat sota el com.
  • Impossibilitat de provar per parts. No pots comprovar «funciona bé la meva lectura de prioritats?» sense executar l'aplicació sencera, navegar fins a l'opció 1 i teclejar a mà.

El que falta és poder donar nom a un tros de codi i fer servir aquest nom quan el necessitis. Això és una funció.

  1. Què és una funció

Una funció és un bloc de codi amb nom que s'escriu una vegada i s'executa tantes vegades com calgui, simplement esmentant el seu nom.

Ja has fet servir funcions des de la primera lliçó de Python: print(), input(), len(), int() i type() són funcions integrades (built-in). Pensa en el que et donen: no tens ni idea de com print aconsegueix que unes lletres apareguin a la pantalla —hi ha allà dins una feina considerable amb el sistema operatiu—, i tanmateix la fas servir sense esforç. És l'abstracció de la lliçó 01-02, aplicada ara al teu propi codi. Una funció ben feta et dona quatre coses:

Benefici Què significa a la pràctica
Reutilització Escrius l'algorisme una vegada i l'invoques des de deu llocs
Nom per a una idea demanar_prioritat() es llegeix de cop; tres línies de while cal desxifrar-les
Verificació per parts Pots provar una funció sola, sense arrencar tota l'aplicació
Canvi localitzat Corregeixes l'algorisme en un únic lloc i es corregeix a tots

Dels quatre, el segon és el que més subestimen els principiants. Una funció no és només un estalvi de teclejar: és una paraula nova en el vocabulari del teu programa. Quan escrius mostrar_menu(), deixes de pensar en print i comences a pensar en menús. Programar bé consisteix, en bona mesura, a construir-se un vocabulari a l'altura del problema.

  1. Sintaxi: def, cos indentat i crida

Definir una funció en Python són dues parts: la capçalera i el cos.

def saludar_equip():
    print("Bon dia, equip d'Estudi Alba.")
    print("Avui revisem les tasques pendents.")

Desmenucem la capçalera, perquè cada símbol hi té el seu paper:

  • def és la paraula clau que anuncia «vaig a definir una funció». Ve de l'anglès define.
  • saludar_equip és el nom. Es tria igual que un nom de variable: snake_case, sense accents ni espais, descriptiu.
  • () són els parèntesis, obligatoris encara que estiguin buits. A dins hi aniran els paràmetres, tema de la lliçó vinent; avui sempre hi aniran buits.
  • : els dos punts tanquen la capçalera i obren el bloc, igual que en un if o un while.

El cos és tot allò indentat a sota (quatre espais, com sempre) i acaba on acaba la indentació: la primera línia que torni al marge esquerre ja és fora de la funció. Un cop definida, la funció es crida escrivint el seu nom seguit de parèntesis. Si afegeixes això sota el bloc anterior:

saludar_equip()
saludar_equip()
Bon dia, equip d'Estudi Alba.
Avui revisem les tasques pendents.
Bon dia, equip d'Estudi Alba.
Avui revisem les tasques pendents.

Dues línies de crida, dues execucions del cos. I fixa't que la definició no imprimeix res per si sola: sense aquestes dues crides, el programa s'executa sense errors i sense cap sortida.

  1. Definir i cridar no són el mateix

Aquesta distinció és la que més s'entravessa al principi. Definir és escriure la recepta: Python llegeix la capçalera, desa el cos a la memòria sota aquell nom i tira endavant, sense executar res. Cridar és cuinar la recepta: Python va a buscar el cos desat i l'executa ara.

Definició Crida
Sintaxi def pausar(): + cos pausar()
Quantes vegades s'escriu Una Les que calguin
Executa el cos? No Sí
Quan passa En llegir el fitxer Al punt exacte de la crida

L'error clàssic és oblidar els parèntesis en cridar:

def pausar():
    input("Prem Intro per continuar...")

pausar     # <-- sense parentesis: NO crida res
pausar()   # <-- amb parentesis: si que la crida

La línia pausar sense parèntesis és vàlida en Python i no dona error. Simplement avalua el nom i obté l'objecte funció, igual que escriure x tot sol avalua la variable x; el resultat es descarta i el programa continua. Al REPL es veu amb claredat:

>>> pausar
<function pausar at 0x7f3c9a1b2e50>
>>> type(pausar)
<class 'function'>

Ja ho tens: sense parèntesis, pausar és un valor de tipus function; amb parèntesis, és una ordre d'execució. Això sembla un parany avui, però és una de les propietats més potents de Python i a Funcions com a valors la aprofitarem a fons. Símptoma típic en un programa real: l'executes, no falla, i senzillament no passa res on esperaves veure alguna cosa. Mira els parèntesis abans que res.

  1. Ordre de definició i execució: el NameError

Python executa el fitxer de dalt a baix. Quan arriba a un def, registra el nom; quan arriba a una crida, busca aquest nom. Per tant: una funció ha d'estar definida abans de la línia que la crida.

mostrar_capcalera()         # ERROR: encara no existeix

def mostrar_capcalera():
    print("TascaFacil - Estudi Alba")
NameError: name 'mostrar_capcalera' is not defined

Un NameError significa exactament això: has fet servir un nom que Python no té registrat. Les tres causes habituals són que l'hagis escrit malament (mostar_capcalera), que el facis servir abans de definir-lo o —ho veurem a Àmbit de variables— que existeixi però no sigui visible des d'on ets.

Matís important: el que ha d'anar abans és la crida, no una altra definició. Una funció pot esmentar en el seu cos una altra que es defineix més avall, perquè el cos no s'executa fins que es crida:

def preparar_pantalla():
    mostrar_capcalera()     # es resol en EXECUTAR, no en definir

def mostrar_capcalera():
    print("TascaFacil - Estudi Alba")

preparar_pantalla()         # aqui ja existeixen totes dues: funciona

Per això l'estructura habitual d'un fitxer Python és: constants a dalt, després totes les definicions, i el codi que les posa en marxa al final. A Descompondre un programa en funcions formalitzarem aquesta estructura.

  1. El flux d'una crida, pas a pas

Quan Python troba una crida passen cinc coses en ordre: s'atura l'execució en aquella línia; s'anota el punt de retorn, el lloc exacte on caldrà tornar; se salta al cos i s'executa sencer, de dalt a baix; en acabar es torna al punt anotat; i es continua amb la línia següent a la crida. Aquest registre d'«on érem» s'anomena pila de crides (call stack): cada crida hi apila un marc i cada retorn el desapila.

flowchart TD
    A["Principal: linia 1"] --> B["Crida a prendre_nota()"]
    B --> C["Cos de prendre_nota"]
    C --> D["Fi del cos: retorn"]
    D --> E["Principal: linia seguent"]

Comprova-ho amb un exemple traçable:

def prendre_nota():
    print("2. Entro a la funcio")
    print("3. Surto de la funcio")

print("1. Abans de la crida")
prendre_nota()
print("4. Despres de la crida")
1. Abans de la crida
2. Entro a la funcio
3. Surto de la funcio
4. Despres de la crida

Els números surten en ordre perquè el programa principal espera: no executa l'última línia fins que la funció ha acabat del tot. És la «prova d'escriptori» de 01-05, ara amb salts. I si una funció en crida una altra, la pila creix: el principal espera la primera, que al seu torn espera la segona. Aquesta capacitat d'imbricar crides sosté programes de qualsevol mida, i portada a l'extrem —una funció que es crida a si mateixa— dona lloc a la recursivitat.

  1. Procediments i funcions que calculen: None

Hi ha dues maneres que una funció sigui útil. Les funcions que fan —els clàssics procediments— valen pel efecte que produeixen: imprimir a la pantalla, demanar una dada, esperar una tecla. Les funcions que calculen valen pel resultat que lliuren a qui les ha cridat, perquè aquest decideixi què en fa. Avui n'escriurem només del primer tipus; el return que fa possible el segon arriba a la lliçó següent. Però hi ha un detall que has de veure ja:

def mostrar_capcalera():
    print("TascaFacil - Estudi Alba")

resultat = mostrar_capcalera()
print(resultat, type(resultat))
TascaFacil - Estudi Alba
None <class 'NoneType'>

Llegeix-ho amb atenció. La funció ha imprès el seu text, però el que ha retornat ha estat None. En Python, tota funció que acaba sense retornar res explícitament retorna None, el valor «absència de valor» que vas conèixer a 02-01. No hi ha funcions que no retornin res: hi ha funcions que retornen None. D'aquí surt un error molt freqüent:

text = print("Hola")      # text val None, no "Hola"

print mostra el text; no el retorna, així que desar-ne el resultat no serveix de res. Imprimir i retornar són operacions diferents, i confondre-les és de les coses que més costa desaprendre.

  1. Docstring i convencions de nom

Si la primera línia del cos d'una funció és una cadena solta, Python la tracta de manera especial: la desa com a documentació. Es diu docstring i s'escriu amb cometes triples.

def mostrar_capcalera():
    """Pinta el titol de l'aplicacio a la pantalla."""
    print("=" * 46)
    print(f"{'TascaFacil - Estudi Alba':^46}")
    print("=" * 46)

Amb això hi guanyes dues coses: l'editor et mostra aquesta frase en escriure la crida, i la funció queda autoexplicada per a qui la llegeixi d'aquí a sis mesos (que gairebé sempre ets tu). Des del REPL, help(mostrar_capcalera) imprimeix la capçalera seguida del seu docstring. Per ara, una regla mínima: una sola línia, començant per un verb, que digui què fa la funció, no com ho fa. El format complet és el tema de Documentació i comentaris. I compte amb la diferència respecte del comentari # de 02-01: el comentari explica una línia concreta a qui llegeix el codi; el docstring descriu la funció sencera a qui la va a fer servir sense mirar a dins.

El nom, per la seva banda, és la meitat del valor d'una funció. Les convencions de la comunitat Python són aquestes:

Regla Bé Malament
snake_case, minúscules i guions baixos mostrar_menu MostrarMenu, mostrarMenu
Comença per un verb en infinitiu demanar_prioritat prioritat, dades
Descriu què fa, no com pausar fer_input_buit
Sense accents ni caràcters estranys mostrar_capcalera mostrar_capçalera
Una responsabilitat, sense «i» al nom validar_titol validar_i_desar_titol

La raó del verb és que una funció és una acció: les variables desen coses (substantius), les funcions fan coses (verbs). En llegir pausar() entens a l'instant que allà hi passa alguna cosa; si hi llegissis pausa, dubtaries entre una variable i una crida. Dos prefixos que veuràs pertot arreu: mostrar_* per al que escriu a la pantalla i demanar_* per al que consulta a l'usuari. Adoptar-los fa el teu codi previsible, que és més elogi del que sembla.

  1. TascaFàcil: les tres primeres funcions

De moment només podem extreure els trossos que no necessiten dades d'entrada ni retornen res, perquè els paràmetres són la lliçó següent. Tot i així hi ha tres candidats evidents: la capçalera, el menú i la pausa.

# tascafacil.py - Estudi Alba
# Versio 0.6.1: primeres funcions sense parametres

AMPLE = 46
PRIORITATS = ("alta", "mitjana", "baixa")
EQUIP = ("Marta", "Luis", "Nuria")
OPCIONS = ("1", "2", "3", "4", "5")


def mostrar_capcalera():
    """Pinta el titol emmarcat de l'aplicacio."""
    print("\n" + "=" * AMPLE)
    print(f"{'TascaFacil - Estudi Alba':^{AMPLE}}")
    print("=" * AMPLE)


def mostrar_menu():
    """Mostra la capcalera i les cinc opcions disponibles."""
    mostrar_capcalera()
    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)


def pausar():
    """Atura l'execucio fins que l'usuari prem Intro."""
    input("\nPrem Intro per tornar al menu...")

I el bucle principal, que ara es llegeix d'una altra manera:

hi_ha_tasca = False
titol = ""
responsable = ""
prioritat = ""
dies = 0
completada = False

while True:
    mostrar_menu()
    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":
        pass                    # registre de la tasca (encara sense extreure)
    elif opcio == "5":
        confirmar = input("Segur que vols sortir? (s/n): ").strip().lower()
        if confirmar == "s":
            print("Fins despres. TascaFacil es tanca.")
            break

    pausar()

Quatre observacions sobre el que acaba de passar:

  • Dotze línies de print s'han convertit en mostrar_menu(). El bucle principal ja es llegeix com el que és: mostrar, llegir, despatxar, pausar. La mecànica de pintar continua existint, però ha deixat de fer nosa.
  • mostrar_menu() crida mostrar_capcalera(). Una funció es pot recolzar en una altra, i així es construeixen nivells d'abstracció.
  • Les funcions llegeixen les constants AMPLE, OPCIONS… encara que no els les passem. Funciona perquè són globals; a Àmbit de variables veuràs per què, i per què les constants són l'única excepció acceptable a la regla de no dependre de globals.
  • pausar() ignora deliberadament el que retorna input(). El seu valor és en l'efecte: esperar. És un procediment de manual.

Fixa't també en les dues línies en blanc entre funcions: és la convenció d'estil de Python (la guia PEP 8, que veurem a 08-05) i ajuda molt a distingir blocs d'una ullada. El que encara no podem extreure són els bucles de validació repetits, perquè cadascun necessita el seu missatge i els seus valors vàlids, i ha de retornar la dada llegida.

Errors Comuns i Consells

Cridar sense parèntesis. pausar avalua l'objecte funció i el llença; pausar() l'executa. No hi ha error ni avís: senzillament no passa res. Si el teu programa «se salta» un pas, mira primer els parèntesis. I si el crides abans de definir-lo, NameError: name 'x' is not defined: col·loca totes les definicions a dalt i el codi que arrenca el programa al final del fitxer.

Oblidar els dos punts o la indentació. def mostrar_menu() sense : dona SyntaxError; el cos sense indentar dona IndentationError, el mateix de 03-01. Una funció necessita almenys una línia indentada: si encara no saps què hi has de posar, escriu pass.

Definir una funció dins del bucle. És legal, però es redefineix a cada volta sense cap benefici i despista en llegir. Les definicions van fora. I no esperis que una funció s'executi sola: definir no és executar.

Creure que print retorna el text. text = print("Hola") deixa text valent None. Mostrar i retornar són coses diferents.

Consell: extreu quan el codi es repeteix dues vegades, no tres. La segona còpia ja és el senyal; la tercera acostuma a arribar amb una diferència subtil entre còpies, que és un error esperant el seu torn. I prova les teves funcions al REPL: enganxa-hi la definició, crida-la i observa. No necessites executar l'aplicació sencera per saber si una peça funciona, i aquest és justament el regal de les funcions.

Exercicis

Exercici 1: Predir la sortida

Sense executar-lo, escriu què imprimeix aquest programa i per què. Para atenció als parèntesis.

def anotar():
    print("Anotat")

print("A")
anotar
print("B")
anotar()
resultat = anotar()
print(resultat)

Exercici 2: Tres funcions per a l'estudi

Escriu panell_alba.py amb tres funcions sense paràmetres, cadascuna amb el seu docstring d'una línia: mostrar_benvinguda(), que imprimeix un marc de 40 guions, el text Panell Estudi Alba centrat i un altre marc; mostrar_equip(), que imprimeix els tres noms de l'equip, un per línia i precedits d'un guió; i acomiadar_se(), que imprimeix una línia en blanc i el missatge Panell tancat.. Després crida les tres en ordre. La constant EQUIP ha d'estar declarada a dalt i fer-se servir a mostrar_equip().

Exercici 3: Arreglar el fitxer trencat

Aquest fitxer té quatre errades relacionades amb funcions. Identifica-les i digues quin error produeix cadascuna, o si no en produeix cap.

mostrar_titol()

def mostrar_titol()
    print("TascaFacil")

def mostrar_peu():
print("Fi de l'informe")

mostrar_peu

Solucions

Solució 1. La sortida és A, B, Anotat, Anotat, None, cadascun a la seva línia. Repassem-ho: print("A") imprimeix A; la línia anotar sense parèntesis avalua l'objecte funció i descarta el resultat, sense imprimir ni fallar; print("B") imprimeix B; anotar() sí que executa el cos i imprimeix Anotat; l'última crida l'executa un altre cop (segon Anotat) i desa a resultat el que retorna, que en no haver-hi return és None.

Solució 2.

# panell_alba.py - Estudi Alba
AMPLE = 40
EQUIP = ("Marta", "Luis", "Nuria")


def mostrar_benvinguda():
    """Pinta el marc i el titol del panell."""
    print("-" * AMPLE)
    print(f"{'Panell Estudi Alba':^{AMPLE}}")
    print("-" * AMPLE)


def mostrar_equip():
    """Llista els noms de l'equip, un per linia."""
    for persona in EQUIP:
        print(f" - {persona}")


def acomiadar_se():
    """Tanca el panell amb un missatge de comiat."""
    print()
    print("Panell tancat.")


mostrar_benvinguda()
mostrar_equip()
acomiadar_se()

El for persona in EQUIP: és el recorregut de 03-02: dins d'una funció funciona exactament igual que fora. I observa l'ordre: les tres definicions a dalt, les tres crides al final. Si moguessis mostrar_benvinguda() a la primera línia del fitxer, tindries un NameError.

Solució 3.

Errada Conseqüència
mostrar_titol() es crida a la línia 1, abans de definir-se NameError en executar
def mostrar_titol() sense els dos punts SyntaxError
El cos de mostrar_peu no està indentat IndentationError
mostrar_peu al final, sense parèntesis Cap error: senzillament no s'executa

Els dos errors de sintaxi es detecten abans d'executar res, així que el programa ni tan sols arriba a produir el NameError. La versió corregida afegeix els dos punts, indenta el print de mostrar_peu, i baixa mostrar_titol() al final del fitxer al costat de mostrar_peu(), aquesta vegada amb parèntesis. La quarta errada és la més perillosa precisament perquè no falla: un programa que calla i no fa res és més difícil de diagnosticar que un que es trenca amb un missatge clar.

Conclusió

Ja saps fabricar les teves pròpies eines. Una funció es defineix amb def nom(): i un cos indentat, es crida escrivint nom() —amb parèntesis, sempre—, i ha d'estar definida abans de la línia que la invoca o Python respondrà amb un NameError. En cridar-la, el programa s'atura, anota on era, executa el cos sencer i torna exactament al punt següent: això és la pila de crides. Les funcions que només produeixen un efecte s'anomenen procediments i retornen None de manera implícita, perquè en Python tota funció retorna alguna cosa. I un docstring d'una línia, començant per un verb, deixa la funció explicada per a qui la faci servir.

Amb això, TascaFàcil ha aprimat una mica: mostrar_capcalera(), mostrar_menu() i pausar() han tret del bucle principal la mecànica de pintar i esperar. Però el problema gros continua intacte: els bucles de validació repetits. I encara no els podem extreure perquè a una funció així cal dir-li quin missatge ha de mostrar i quins valors accepta, i aquesta funció ens ha de lliurar la dada ja validada. Dir dades a una funció són els paràmetres; que ens lliuri un resultat és el return. Aquestes dues peces, que són les que converteixen una funció en una cosa veritablement reutilitzable, són el tema de la lliçó vinent: Paràmetres i retorn de valors.

© Copyright 2026. Tots els drets reservats