En tancar el mòdul 2 vam deixar un diagnòstic clar: el codi de Papyrus funciona, però viu dispers. La cerca de llibres és dins de menu.py, el càlcul del preu de soci es repeteix a venda_soci.py i a tiquet.py, i cada millora obliga l'Anna a tocar diversos scripts. La solució promesa són les funcions: blocs de codi amb nom propi que defineixes una vegada i executes tantes vegades com vulguis. I aquí va la bona notícia: en fas servir des del primer dia — print(), len(), input(), enumerate() són funcions que ja venien fetes. En aquesta lliçó aprendràs a crear les teves amb def, a retornar resultats amb return, a documentar-les amb docstrings i a entendre on "viuen" les seves variables. Com a projecte, començarem la reorganització de menu.py extraient-ne les dues primeres peces: mostrar_cataleg() i cercar_llibre().
Contingut
- Què és una funció i per què fer-la servir
- Definir amb
defi cridar la funció - Paràmetres: dades d'entrada
return: retornar resultats (i el casNone)- Docstrings: documentar la funció
- Àmbit de les variables: local davant de global
- Projecte: primera refactorització de
menu.py
Què és una funció i per què fer-la servir
Una funció és un bloc de codi amb nom, que opcionalment rep dades d'entrada (paràmetres) i opcionalment retorna un resultat. Pensa en la fotocopiadora de Papyrus: l'Anna no reconstrueix la màquina cada vegada que necessita una còpia; hi posa un original (entrada), prem un botó (crida) i recull la còpia (resultat).
Les tres raons clàssiques per fer servir funcions:
| Motiu | Sense funcions | Amb funcions |
|---|---|---|
| Reutilització | El càlcul del preu de soci està copiat a 3 scripts | S'escriu una vegada i es crida des d'on calgui |
| Manteniment | Canviar l'IVA obliga a editar cada còpia (i oblidar-ne una = bug) | Es canvia en un únic lloc |
| Llegibilitat | menu.py és un bloc de 35 línies que cal llegir sencer |
mostrar_cataleg() s'explica sola pel seu nom |
Definir amb def i cridar la funció
La sintaxi mínima:
Desglossament línia a línia:
def(de define) inicia la definició.saludarés el nom: les mateixes regles que les variables (snake_case, PEP 8). Sol ser un verb, perquè una funció fa alguna cosa.- Els parèntesis
()contindran els paràmetres (aquí, cap) i els dos punts:obren el bloc. - El cos va indentat, exactament igual que en un
ifo unfor. saludar()— amb parèntesis — crida (executa) la funció. Sense parèntesis,saludarnomés anomena la funció sense executar-la; és un error silenciós molt habitual.
Un detall important: definir no és executar. Python llegeix el def, memoritza el bloc sota aquest nom i continua endavant. El cos només corre quan algú crida la funció. Per això aquest script imprimeix en un ordre que sorprèn els principiants:
def acomiadar():
print("2. Dins de la funcio")
print("1. Abans de cridar")
acomiadar()
print("3. Despres de cridar")sequenceDiagram
participant S as Script principal
participant F as acomiadar()
S->>S: print("1. Abans de cridar")
S->>F: crida acomiadar()
F->>F: print("2. Dins de la funcio")
F-->>S: fi de la funció (torna al punt de la crida)
S->>S: print("3. Despres de cridar")
Paràmetres: dades d'entrada
Una funció sense entrades sempre fa el mateix. Els paràmetres la fan flexible:
def etiquetar(titol, preu):
print(f"{titol:<12} {preu:>6.2f} €")
etiquetar("Hamlet", 9.95)
etiquetar("Faust", 21.00)titolipreusón paràmetres: noms que la funció fa servir internament."Hamlet"i9.95són arguments: els valors concrets que envies a la crida. El primer va al primer paràmetre, el segon al segon (per posició).- A cada crida, els paràmetres neixen amb els valors rebuts, la funció treballa i en acabar desapareixen.
De moment en tenim prou amb aquesta forma bàsica; la lliçó 03-02 està dedicada per complet als tipus d'arguments (amb nom, valors per defecte, quantitats variables...).
return: retornar resultats (i el cas None)
print() mostra, però no lliura. Si vols fer servir el resultat d'una funció en un càlcul posterior, necessites return:
def preu_soci(base):
"""Preu per a socis: descompte del 5% i despres IVA del 4%."""
final = base * (1 - 0.05) * (1 + 0.04)
return round(final, 2)
preu_hamlet = preu_soci(9.95)
print(f"Lluis paga {preu_hamlet} €") # Lluis paga 9.83 €
print(f"Dos exemplars: {preu_soci(9.95) * 2} €")Claus de return:
- Lliura un valor al punt de la crida: l'expressió
preu_soci(9.95)val9.83, i pots assignar-la, imprimir-la o operar-hi. - Acaba la funció immediatament: el codi posterior al
returnexecutat no corre mai. Això el converteix en la sortida d'emergència perfecta — recorda el consell del mòdul 2: allà onbreaknomés surt del bucle més intern,returnsurt de la funció sencera, bucles imbricats inclosos. - Una funció pot tenir diversos
return(per exemple, un dins d'unifi un altre al final); a cada crida se n'executa com a molt un.
I si una funció no té return, o escriu return a seques? Retorna None, el "valor buit" que vas conèixer al mòdul 1:
def avisar_esgotat(titol):
print(f"'{titol}' esta esgotat.")
resultat = avisar_esgotat("Faust")
print(resultat) # NoneLa taula que evita el 90 % de les confusions inicials:
print(x) dins de la funció |
return x |
|
|---|---|---|
| Mostra res per pantalla? | Sí | No (llevat que qui crida ho imprimeixi) |
| La crida té un valor útil? | No (val None) |
Sí (val x) |
| Es pot continuar operant amb el resultat? | No | Sí |
| Ús típic | Informar l'usuari | Calcular per a un altre codi |
Regla pràctica per a Papyrus: les funcions que calculen (preus, cerques) han de retornar amb return; les que presenten (mostrar el catàleg) poden limitar-se a print() i retornar None.
Docstrings: documentar la funció
La primera línia del cos, si és una cadena, es converteix en la docstring: la documentació oficial de la funció. S'escriu entre cometes triples:
def preu_soci(base):
"""Retorna el preu final per a socis de Papyrus.
Aplica el descompte de soci (5%) i despres l'IVA de llibres (4%),
arrodonint a 2 decimals.
"""
return round(base * (1 - 0.05) * (1 + 0.04), 2)
help(preu_soci) # mostra la docstring al REPL- No és un comentari decoratiu:
help(), VS Code (en passar el ratolí per sobre de la crida) i les eines de documentació la llegeixen. - Convenció: primera línia en forma de resum que càpiga en una línia; si cal més detall, línia en blanc i paràgraf explicatiu.
- Els comentaris
#expliquen com funciona un fragment per dins; la docstring explica què fa la funció i com fer-la servir des de fora.
Àmbit de les variables: local davant de global
On "viu" una variable? Depèn d'on s'assigni:
NOM_BOTIGA = "Papyrus" # ambit global (nivell de l'script)
def crear_tiquet():
linia = f"Tiquet de {NOM_BOTIGA}" # linia: ambit LOCAL
return linia
print(crear_tiquet()) # Tiquet de Papyrus
print(linia) # NameError: name 'linia' is not definedliniaés local: neix en cridar la funció i mor en acabar. A fora, no existeix. Això és un avantatge enorme: dues funcions poden fer servirlinia,iototalsense trepitjar-se.NOM_BOTIGAés global: es va assignar al nivell de l'script, i les funcions poden llegir-la sense fer res d'especial. Python, en no trobarNOM_BOTIGAa l'àmbit local, la busca "un pis més amunt".
El matís delicat arriba en intentar modificar una global dins d'una funció:
En veure l'assignació caixa = ..., Python decideix que caixa és local a cobrar... i llavors caixa + quantia intenta llegir una local que encara no té valor. Existeix una paraula clau que ho "arregla":
global caixa diu a Python "quan assigni caixa, fes servir la global". Funciona, però convé evitar-la: una funció que modifica globals té efectes invisibles des de la crida — llegint cobrar(12.35) ningú no sospita que caixa ha canviat, i amb diverses funcions tocant les mateixes globals el programa es torna indepurable. L'alternativa neta és rebre i retornar:
def cobrar(caixa, quantia):
"""Retorna el nou total de caixa despres de cobrar la quantia."""
return caixa + quantia
caixa = 0.0
caixa = cobrar(caixa, 12.35) # el canvi es VEU al punt de la cridaResum de regles d'àmbit:
| Operació dins de la funció | Necessita res d'especial? | Recomanable? |
|---|---|---|
Llegir una global (constants com IVA_LLIBRES) |
No | Sí — és l'ús natural de les constants |
| Assignar una variable nova | No (serà local) | Sí |
| Modificar una global | Sí, global nom |
No — rep la dada com a paràmetre i retorna el resultat |
Projecte: primera refactorització de menu.py
Complim la promesa del mòdul 2. Refactoritzar és reorganitzar codi sense canviar el que fa, i menu.py ho demana a crits: extraurem l'opció 1 a mostrar_cataleg() i el cor de l'opció 2 a cercar_llibre(). A la carpeta papyrus (amb el .venv activat), la nova versió:
NOM_BOTIGA = "Papyrus"
cataleg = ["L'Odissea", "Hamlet", "El Quixot"]
preus = [12.50, 9.95, 15.90]
estocs = [4, 6, 8]
def mostrar_cataleg():
"""Imprimeix la taula de titols, preus i estoc."""
print(f"\n{'Titol':<12} {'Preu':>8} {'Estoc':>6}")
print("-" * 28)
for i, titol in enumerate(cataleg):
print(f"{titol:<12} {preus[i]:>6.2f} € {estocs[i]:>6}")
def cercar_llibre(cercat):
"""Retorna l'index del llibre amb titol coincident, o None si no hi es."""
for i, titol in enumerate(cataleg):
if titol.lower() == cercat.lower():
return i
return None
print(f"Benvinguda a la gestio de {NOM_BOTIGA}")
while True:
print()
print("1. Veure cataleg")
print("2. Cercar un llibre")
print("3. Sortir")
opcio = input("Tria una opcio (1-3): ")
match opcio:
case "1":
mostrar_cataleg()
case "2":
cercat = input("Titol a cercar: ")
if not cercat:
print("No has escrit res.")
continue
index = cercar_llibre(cercat)
if index is None:
print(f"'{cercat}' no es al cataleg.")
else:
print(f"'{cataleg[index]}': {preus[index]:.2f} €, estoc {estocs[index]}.")
case "3":
print(f"Tancant {NOM_BOTIGA}. Fins dema, Anna!")
break
case _:
print("Opcio no valida. Tria 1, 2 o 3.")Fixa't en les decisions de disseny:
mostrar_cataleg()presenta: imprimeix i retornaNone; elwhilequeda llegible — l'opció 1 és ara una sola línia que es llegeix com una frase.cercar_llibre()calcula: substitueix el patrófor/break/elsede la versió anterior per una cosa encara més directa —return italla la cerca tan bon punt hi ha coincidència (fa el paper delbreak) i elreturn Nonefinal cobreix el cas "no trobat" (el paper de l'elsedel bucle). Retornar l'índex en comptes d'imprimir permet que qui crida decideixi què fer-ne; ens ho agrairem a la lliçó 03-04.if index is Nonedistingeix "no trobat" d'un resultat vàlid; compte,if not indexseria un bug, perquè l'índex0(L'Odissea) també és falsy — truthiness del mòdul 2 aplicada.- Les funcions llegeixen les globals
cataleg,preusiestocsperò no les modifiquen: ús legítim de l'àmbit global. Quan extraiem aquestes funcions a un mòdul propi (03-04) veuràs com eliminar també aquesta dependència.
El comportament del menú és idèntic al del mòdul 2 — aquesta és la gràcia de refactoritzar — però ara la cerca és reutilitzable: tiquet.py o venda_soci.py podran fer servir cercar_llibre() tan bon punt sapiguem compartir codi entre fitxers (lliçó 03-04).
Errors Comuns i Consells
- Cridar sense parèntesis:
mostrar_cataleg(sense()) no executa res ni dona error; simplement anomena la funció. Si "la funció no fa res", revisa els parèntesis. - Fer servir la funció abans de definir-la: Python llegeix el fitxer de dalt a baix; cridar
cercar_llibre()en una línia anterior al seudefprodueixNameError. Convenció pràctica: constants i dades a dalt, després elsdef, i el programa principal al final. - Confondre
printambreturn: sitotal = preu_soci(9.95)et donaNone, gairebé segur que la funció imprimeix en comptes de retornar. Repassa la taula comparativa d'aquesta lliçó. - Codi després del
return: qualsevol línia després d'unreturnque sempre s'executa és codi mort. VS Code l'atenua visualment; esborra'l o replanteja la lògica. - Abusar de
global: si et descobreixes escrivintglobalen diverses funcions, el disseny coixeja. Passa les dades com a paràmetres i retorna els resultats. - Oblidar la docstring: per a funcions de projecte (les que altres —o tu d'aquí a un mes— reutilitzaran), una línia de docstring és el mínim educat.
- Consell: una funció ha de fer una cosa. Si en descriure-la necessites la paraula "i" ("mostra el catàleg i demana un llibre i el cerca"), probablement són dues o tres funcions.
Exercicis
Exercici 1: fitxa amb i sense return
Escriu dues funcions: hi_ha_estoc(unitats), que rebi un nombre d'unitats i retorni True si és més gran que 0 i False en cas contrari; i mostrar_disponibilitat(titol, unitats), que faci servir la primera per imprimir '<titol>': disponible o '<titol>': esgotat. Prova-les amb Hamlet (6 unitats) i Faust (0 unitats). Afegeix docstring a totes dues.
Exercici 2: comptar esgotats sense tocar globals
Donada la llista global estocs = [4, 6, 8, 0], escriu comptar_esgotats(llista_estocs) que rebi una llista d'estocs per paràmetre i retorni quants valen 0 (patró comptador del mòdul 2). Crida-la amb estocs i imprimeix Llibres esgotats: 1. Comprova que la funció funciona igual amb [0, 0, 3]. Per què és millor rebre la llista com a paràmetre que llegir la global directament?
Exercici 3: el detectiu de l'àmbit
Sense executar-lo, prediu què imprimeix aquest script i per què. Després executa'l i comprova la teva hipòtesi:
missatge = "caixa tancada"
def obrir_caixa():
missatge = "caixa oberta"
return missatge
print(obrir_caixa())
print(missatge)Solucions
Exercici 1:
def hi_ha_estoc(unitats):
"""Retorna True si queden unitats disponibles."""
return unitats > 0
def mostrar_disponibilitat(titol, unitats):
"""Imprimeix si un titol esta disponible o esgotat."""
if hi_ha_estoc(unitats):
print(f"'{titol}': disponible")
else:
print(f"'{titol}': esgotat")
mostrar_disponibilitat("Hamlet", 6) # 'Hamlet': disponible
mostrar_disponibilitat("Faust", 0) # 'Faust': esgotatObserva que return unitats > 0 retorna directament el booleà de la comparació: no cal if unitats > 0: return True else: return False. I una funció pot cridar-ne una altra: és la base de construir programes per capes.
Exercici 2:
estocs = [4, 6, 8, 0]
def comptar_esgotats(llista_estocs):
"""Retorna quants elements de la llista valen 0."""
esgotats = 0
for unitats in llista_estocs:
if unitats == 0:
esgotats += 1
return esgotats
print(f"Llibres esgotats: {comptar_esgotats(estocs)}") # 1
print(comptar_esgotats([0, 0, 3])) # 2Rebre la llista com a paràmetre fa la funció reutilitzable amb qualsevol llista (la de Papyrus avui, la d'una altra botiga demà, una llista de prova en un test) i deixa clar a la crida quines dades fa servir. La variable esgotats és local: no embruta l'script.
Exercici 3: Imprimeix caixa oberta i després caixa tancada. L'assignació missatge = "caixa oberta" dins de la funció crea una variable local que amaga la global mentre la funció corre; la global no canvia mai. Perquè la funció modifiqués la global caldria global missatge — que, com hem vist, és millor evitar retornant el valor (just el que fa el return).
Conclusió
Ja saps crear funcions amb def, distingir paràmetres (entrada) de return (sortida), documentar-les amb docstrings i raonar sobre l'àmbit de les seves variables: locals que neixen i moren amb cada crida, globals que es poden llegir (constants) però que no convé modificar amb global. Amb això, menu.py ha fet el primer pas de "script dispers" a "programa organitzat": mostrar_cataleg() i cercar_llibre() tenen nom, docstring i una única responsabilitat. Però les nostres funcions encara són rígides: cercar_llibre() rep un únic argument posicional i no hi ha manera de donar-li opcions. A la lliçó següent explorarem a fons els arguments de funció — amb nom, valors per defecte, quantitats variables amb *args i **kwargs — i saldarem un deute pendent del mòdul 2: entendre de debò zip(), l'eina que recorre en paral·lel cataleg, preus i estocs.
Curs de Programació en Python
Mòdul 1: Introducció a Python
- Introducció a Python
- Configuració de l'Entorn de Desenvolupament
- Sintaxi de Python i Tipus de Dades Bàsics
- Variables i Constants
- Entrada i Sortida Bàsica
- Entorns Virtuals i Gestió de Paquets
Mòdul 2: Estructures de Control
Mòdul 3: Funcions i Mòduls
- Definició de Funcions
- Arguments de Funció
- Funcions Lambda
- Mòduls i Paquets
- Visió General de la Biblioteca Estàndard
Mòdul 4: Estructures de Dades
Mòdul 5: Programació Orientada a Objectes
Mòdul 6: Gestió de Fitxers
- Lectura i Escriptura de Fitxers
- Treball amb Fitxers CSV
- Gestió de Dades JSON
- Operacions amb Fitxers i Directoris
Mòdul 7: Gestió d'Errors i Excepcions
- Introducció a les Excepcions
- Gestió d'Excepcions
- Llançament d'Excepcions
- Excepcions Personalitzades
- Bones Pràctiques i Registre d'Errors amb logging
Mòdul 8: Temes Avançats
- Anotacions de Tipus (type hints)
- Decoradors
- Generadors
- Gestors de Context
- Concurrència: Fils i Processos
- Asyncio per a la Programació Asíncrona
Mòdul 9: Proves i Depuració
- Introducció a les Proves
- Proves Unitàries amb unittest
- Proves amb pytest
- Desenvolupament Guiat per Proves
- Tècniques de Depuració
- Ús de pdb per a la Depuració
Mòdul 10: Desenvolupament Web amb Python
- Introducció al Desenvolupament Web
- Fonaments del Framework Flask
- Construcció d'APIs REST amb Flask
- Introducció a Django
- Construcció d'Aplicacions Web amb Django
Mòdul 11: Ciència de Dades amb Python
- Introducció a la Ciència de Dades
- NumPy per a la Computació Numèrica
- Pandas per a la Manipulació de Dades
- Matplotlib per a la Visualització de Dades
- Introducció a l'Aprenentatge Automàtic amb scikit-learn
