Introducció a les funcions
En aquesta nova unitat aprendreu a utilitzar, definir i a crear les funcions adequades per a cada programa. D’entrada analitzareu el concepte intuïtiu de funció i revisareu l’ús de les funcions del sistema que heu estat utilitzant fins ara. Per acabar, aprendreu a definir i a utilitzar les vostres pròpies funcions.
Funcions predefinides
Fins ara heu utilitzat funcions predefinides del Python que heu incorporat als vostres programes, tant si són del sistema com d’altres biblioteques. Començarem per definir i clarificar alguns conceptes sobre les funcions.
Concepte de funció
La idea de funció es pot entendre com una relació entre dos elements o conjunts d’elements on cada element d’un dels conjunts (el conjunt d’entrada) està relacionat amb exactament un element de l’altre (el conjunt de sortida)
És a dir una funció és un mecanisme o procés mitjançant el qual, a cada dada d’entrada se li assigna una única dada de sortida. Per moltes vegades que tornem a aplicar-lo, a la mateixa entrada li correspondrà sempre la mateixa sortida.
Una altra manera (més matemàtica) de dir-ho és: una funció pren un argument x com a entrada, del qual en treu un valor de sortida f(x).
L’entrada d’una funció s’anomena argument, i la sortida s’anomena valor.
Per començar, repassarem alguns exemples de funcions matemàtiques.
Exemples de funcions matemàtiques
Esteu acostumats a utilitzar moltes funcions matemàtiques que tenen els nombres (enters o reals) com a arguments d’entrada i com a valors de sortida. Per exemple, si x és un nombre qualsevol, les següents funcions transformen la x d’entrada en un altre nombre.
- f(x) = x * x. És una funció que retorna el quadrat de l’entrada (el nombre 1 el transforma en 1, el -2 en 4, etc.).
- g(x) = -x. És una funció que canvia el signe de l’entrada (el nombre 1 el transforma en el -1, el -2 en el 2 etc.).
- abs(x). És una funció que retorna el valor absolut de l’entrada (el nombre 1 el transforma en el 1, i el -2.5 en el 2.5).
- espai = velocitat * temps. És una funció que rep dues dades d’entrada (la velocitat i el temps), i calcula una dada de sortida (l’espai recorregut). Per exemple, si la velocitat és de 60 km/h i el temps és de 2h, l’espai recorregut serà de 120km.
Fixeu-vos en l’últim exemple: hi ha funcions que poden tenir més d’un argument d’entrada.
També sabeu com utilitzar algunes funcions definides pel sistema o d’alguna de les llibreries de Python. En el cas de les funcions d’un llenguatge informàtic, les entrades seran dades d’algun tipus de dada del llenguatge, igual que els valors de sortida.
Normalment, una funció té limitacions en tant al tipus de dades que accepta. És possible que a més del tipus de dades, existeixin limitacions en els arguments per entrar en una funció (per exemple, math.sqrt() només accepta valors numèrics positius).
Exemples de funcions definides en el Python
- abs(x). És una funció que rep una dada de tipus int o float i retorna un altre int o float, que és el seu valor absolut (el nombre 1 el transforma en l’1, i el -2.5 en el 2.5).
- len(x). És una funció que rep una dada de tipus str o list (entre altres tipus), i retorna la quantitat de caràcters o d’elements que la formen. Per exemple, si x és la cadena
'hola'retornarà el nombre enter 4. - pow(x, y). És una des les funcions predefinides del sistema que eleva la x a la potència y. Per exemple,
pow(2, 3)retorna 2 elevat a 3, que és el nombre 8. Ipow(4, 0.5)retorna 4 elevat a 0.5, que és el nombre 2.0. - input(). És una funció que es pot cridar sense argument, i el valor de retorn es “carrega” des de l’entrada estàndard (que per defecte és el teclat). Retorna una dada de tipus str que conté el que s’ha llegit des de teclat.
Fixeu-vos que les funcions del Python (i dels altres llenguatges de programació), treballen amb tipus de dades del llenguatge. Poden operar amb molts altres tipus més enllà dels numèrics.
En termes més senzills i intuïtius, podeu pensar en una funció com una “màquina” que pren un valor (o conjunt de valors) com a entrada i que produeix un altre valor com a sortida, seguint una regla o un procediment definit.
Exemples d'altres funcions
- Es pot definir una funció que assigna cada persona al mes que ha nascut. Per exemple, al Pere, que ha nascut el 23/01/2003, li assigna el mes de gener. A la Maria, que és del 23/03/1990 li assigna el mes de març.
- Una altra funció seria assignar a cada habitatge d’una ciutat un nombre que indiqui la quantitat de persones que hi estan empadronades.
- En una màquina expenedora, hi ha una funció que assigna a cada producte ofert un número d’un conjunt (el preu).
Per començar a entendre una funció, el primer que cal fer és definir quins són els arguments d’entrada, i quins poden ser els valors de sortida.
El domini d’una funció és el conjunt de tots els valors d’entrada o dades per als quals la funció està definida. En altres paraules, és l’interval o conjunt de valors que podem passar a la funció per aconseguir una sortida definida. Són els valors que estan “autoritzats” a entrar a la funció.
El codomini d’una funció és el conjunt de tots els possibles valors de sortida o resultats que la funció pot produir. És l’interval o conjunt de valors que podem obtenir com a resultat de la funció.
Domini i codomini de funcions
- abs(x). El domini de la funció serien tots els nombres reals (si parlem de matemàtiques) o int i float (si parlem de la funció predefinida de Python). El codomini serien només els positius (amb el zero inclòs).
- math.sqrt(x). El domini serien els enters i float positius (amb el zero). Si interpretem la funció entenent que el domini són els float, el codomini serien també els float positius. Però si considerem que el domini són només els enters positius, el codomini estaria format per nombres float concrets.
- len(x). El domini seria el tipus de dades str o list (i altres tipus de Python). El codomini serien els enters positius (amb el zero).
- Màquina de exependora. El domini seria el conjunt de nombres que podem demanar, i el codomini el conjunt de productes oferts.
Si interpretem que el domini de la funció math.sqrt són els enters positius, en el codomini hi hauria els nombres float 0.0 (arrel de 0), 1.0 (arrel de 1), 1.4142135623730951 (arrel de 2), 1.7320508075688772 (arrel de 3), 2.0 (arrel de 4)… Però no tots els nombres float que es troben entre el 1.0 i el 1.4142.., per exemple).
Des del punt de vista informàtic, una funció és un bloc de codi reutilitzable que du a terme una tasca específica. Les funcions predefinides són aquelles que ja estan implementades en el llenguatge de programació i es poden utilitzar sense haver de definir-les.
El domini d’una funció informàtica fa referència als tipus de dades o valors que pot acceptar com a entrada, mentre que el codomini fa referència als valors que pot produir com a sortida.
Els llenguatges de programació tipats com C requereixen que es declari el tipus de dada que una funció pot acceptar com a entrada i el tipus de dada que retorna com a sortida. Això proporciona una major seguretat a l’hora de programar, ja que impedeix que es passin dades de tipus incorrectes a les funcions.
En Python, els tipus de dades són dinàmics, el que significa que no cal declarar-los explícitament. Tot i això, el concepte de tipus de dades incorrectes encara és rellevant. Python permet passar qualsevol tipus de dada a una funció, però si la funció no està dissenyada per manipular aquest tipus, pot provocar errors o resultats inesperats.
La signatura d’una funció és la definició de la seva interfície, que detalla quins paràmetres pot rebre i què retornarà.
Com ja heu après, per utilitzar una funció de Python podeu utilitzar la documentació en línia per consultar la signatura d’una funció, utilitzant la funció help().
La funció abs()
Si consulteu la documentació a través de l’intèrpret interactiu amb l’ordre help(abs), obteniu el següent:
Help on built-in function abs in module builtins:
abs(x, /)
Return the absolute value of the argument.
La documentació en Python sol ser molt descriptiva, i com que no és un llenguatge tipat, fuig de determinar el domini de la funció (en aquest cas no especifica que les entrades hagin de ser enters o valors float).
Fa una descripció una mica vaga, però apel·lant al sentit comú de l’usuari. Qui vol utilitzar aquesta funció ha de saber què és el valor absolut i sobre quins tipus de dades té sentit calcular-lo.
Significat de /
La / que observeu al final de la signatura, indica que aquí acaben els paràmetres posicionals i comencen els paràmetres nominals.
Els paràmetres nominals, també coneguts com a paràmetres de nom o paràmetres amb nom explícit, són aquells paràmetres d’una funció que s’han de passar utilitzant el seu nom en la crida de la funció, en lloc de ser passats per ordre de posició. Això significa que, quan crideu a la funció, heu de proporcionar els noms dels paràmetres als quals voleu assignar valors.
Fixeu-vos en aquest exemple de la funció print(): els primers paràmetres són posicionals i després n’hi ha de nominals (que són opcionals, i que tenen un valor per defecte).
>>> nom = 'Pere'
>>> edat = '23'
>>> print('em dic', nom, 'i tinc' , edat ,'anys')
em dic Pere i tinc 23 anys
>>>
>>> print('em dic', nom, 'i tinc' , edat ,'anys', sep='_', end='!!!')
em dic_Pere_i tinc_23_anys!!!>>>
Si la funció print() es crida sense especificar el paràmetre sep, per defecte és l’espai en blanc. El valor per defecte de end és el salt de línia.
Quan veieu una barra inclinada (/) en la signatura d’una funció de Python, com en l’exemple abs(x, /), aquesta barra indica que tots els paràmetres abans de la barra són paràmetres posicionals, és a dir, han de ser passats per ordre de posició, mentre que els paràmetres després de la barra són paràmetres nominals, i han de ser passats explícitament pel seu nom.
Aquesta característica s’introdueix a Python 3.8 com a part de la sintaxi de les signatures de les funcions. La utilització de la barra / ajuda a definir i limitar l’ús de paràmetres nominals, millorant la claredat i la consistència del codi.
Es va considerar oportú no modificar la signatura de la funció print() incorporant la barra: la seva pàgina indica:
print(*args, sep=' ', end='\n', file=None, flush=False)
Prints the values to a stream, or to sys.stdout by default.
En canvi, sí que s’ha incorporat en altres funcions com sorted():
sorted(iterable, /, *, key=None, reverse=False)
Return a new list containing all items from the iterable in ascending order.
En el següent codi podeu veure que si apliqueu la funció abs() sobre un valor numèric funciona bé. Però si l’apliqueu sobre una cadena o bé sobre una llista, generarà un error:
>>> abs(-3)
3
>>> abs(2.234)
2.234
>>> abs('hola')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: bad operand type for abs(): 'str'
>>> abs([3])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: bad operand type for abs(): 'list'
Domini i funcions en llenguatges tipats o no tipats.
El Python és un llenguatge no tipat. És a dir, no cal definir el tipus d’una variable abans d’utilitzar-la, i es pot modificar de manera dinàmica. El mateix passa amb els arguments que entren a les funcions (domini), o amb els valors de retorn.
Fixeu-vos en les diferències entre la documentació relativa a una mateixa funció en un llenguatge tipat (com el C) i el Python. La signatura de la funció abs() en Python és:
abs(x, /)
Return the absolute value of the argument.
Aquesta descripció proporciona una explicació senzilla de la funció. No dona, però, detalls del domini de la funció, ni indica per a quin tipus de dada d’entrada és dirigida. És l’usuari qui ha de pensar que el valor absolut es pot aplicar a dades de tipus int i float.
D’altra banda, en C o C++, la signatura de la funció abs() seria:
int abs(int n); long int abs(long int n);En aquest cas és més tècnica. Especifica que la funció pot prendre un enter o un long int com a argument i retorna una dada del mateix tipus. Tot i que no proporciona molts detalls sobre com funciona la funció, és clara en els tipus d’arguments que accepta i en el tipus de dada que retorna.
Si un programador intenta utilitzar aquesta funció abs() amb una dada de tipus str, el compilador de C no li permetrà. En canvi, l’estratègia de les funcions de Python és acceptar qualsevol dada com a entrada, i si dins de la funció es produeix un error, el llencen i si no es recull, es finalitza l’execució de manera brusca.
Mètodes de classe
Fins ara s’ha parlat de funcions, però també cal explicar què són els mètodes. La programació orientada a objectes és un paradigma de programació que intenta proporcionar un model basat en objectes que contenen dades i procediments associats, coneguts com a mètodes.
El Python és un llenguatge orientat a objectes, de manera que els objectes poden tenir mètodes.
Un mètode no és més que una funció que pot engegar un objecte sobre si mateix. S’invoca el mètode amb el nom d’una instància (un objecte concret) seguit d’un punt i el nom del mètode. Seguidament, s’han de posar els parèntesis de les funcions i els arguments que es passen al mètode.
En el codi següent podeu veure un exemple de com definim una dada anomenada cadena que és un objecte, i com, sobre aquesta, cridem al mètode isDigit():
>>> cadena = '201' >>> cadena.isdigit() True
Com que el mateix objecte engega el mètode, no cal que es passi a ell mateix per argument, de manera que “enganya” una mica.
El mètode isdigit() de la classe str
Si consulteu la documentació d’aquest mètode (help(str.isdigit)), obteniu la pàgina següent:
Help on method_descriptor:
isdigit(self, /)
Return True if the string is a digit string, False otherwise.
A string is a digit string if all characters in the string are digits and there
is at least one character in the string.
self. En Python,selfés una convenció que s’utilitza per fer referència a l’objecte sobre el qual s’està cridant el mètode. En mètodes de classe, el primer paràmetre s’espera que siguiself, que representa l’objecte mateix. Quan crideu un mètode sobre un objecte, com objecte.metode(), Python passa automàticament l’objecte com a primer argument a la funció, que es nomenaselfper convenció. No cal proporcionar-lo explícitament al cridar el mètode, ja que Python ho gestiona internament./: En la signatura del mètodeisdigit(), la barra/indica el final dels paràmetres posicionalment obligatoris.
En aquest cas, la documentació indica els valors de sortida i explica què fa el mètode.
Aquesta informació us ha de permetre a interpretar els resultats en les operacions següents:
- En el primer cas, podeu veure com es defineix la variable
cadenaque serà un objecte sobre el qual es guarda una cadena de caràcters.
>>> cadena = '123' >>> cadena.isdigit() True >>> cadena = 'hola' >>> cadena.isdigit() False >>> '12.3'.isdigit() False
- En cas que no es defineixi la variable explícitament, Python la crearà en memòria per permetre cridar un mètode sobre aquesta:
>>> '12.3'.isdigit() False
- Però si intenteu crear-la sobre un valor que no es pot convertir a variable ni pot ser un nom de variable, tindreu un error:
>>> 12.isdigit()
<stdin>:1: SyntaxWarning: invalid decimal literal
File "<stdin>", line 1
12.isdigit()
SyntaxError: invalid syntax
- Finalment, si crideu al mètode amb un paràmetre, també us donarà un error perquè no n’espera cap:
>>> '12'.isdigit(3) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: str.isdigit() takes no arguments (1 given)
Documentació dels mètodes i les funcions
En aquest apartat s’aprofundirà en la interpretació de la documentació proporcionada per l’ajuda en línia. L’objectiu és desxifrar els tipus de dades dels arguments d’entrada i dels valors de retorn que proporcionen les funcions.
Per poder utilitzar una funció predefinida és indispensable no només proporcionar-li els tipus de dades correctes (i evitar errors o resultats inesperats), sinó entendre què retornaran perquè el programa que utilitza la funció en pugui fer un bon ús.
Tipus de dades d'entrada i sortida del mètode str.replace()
Si consulteu la documentació d’aquest mètode (amb help(str.replace) ), obteniu la pàgina següent:
Help on method_descriptor:
replace(self, old, new, count=-1, /)
Return a copy with all occurrences of substring old replaced by new.
count
Maximum number of occurrences to replace.
-1 (the default value) means replace all occurrences.
If the optional argument count is given, only the first count occurrences are
replaced.
Cal que, amb l’ajuda de l’intèrpret interactiu, aprengueu a utilitzar el mètode i pugueu definir amb rigor els tipus d’entrada i de retorn.
La documentació indica que:
replace()s’ha de cridar proporcionant 2 arguments obligatoris (oldinew), i un opcional (count), que té un valor per defecte. Si no hi ha tercer argument,countprendrà el valor -1).selfindica que actua sobre la cadena que el crida. La barra indica la fi dels paràmetres obligatoris.- El tipus de dada de
countestà clar que ha de ser un enter. - Els tipus que poden prendre els paràmetres
oldinewno estan determinats explícitament. Però podeu deduir que es tracta de cadenes.
Ara cal que feu proves amb l’intèrpret interactiu per aprendre a utilitzar el mètode replace().
>>> a = 'hola'
>>> b = 'patata'
>>> a.replace('h', 't')
'tola'
>>> b.replace('ta', 'pap')
'papappap'
>>> b.replace('ta', 'pap', 1)
'papapta'
>>> b.replace('ta', 'pap', 0)
'patata'
>>> b.replace('ta', 'pap', 7)
'papappap'
>>> b.replace('ta', 'pap', -3)
'papappap'
Podeu descriure els tipus dels valors d’entrada i sortida així:
Entrada: str, str, int (opcional)
Sortida: str
Definir funcions en Python
Fins ara, hem explorat l’ús de les funcions i mètodes predefinits en Python. Ara, és el moment d’aprendre a definir les vostres pròpies funcions, el que us permetrà crear blocs de codi independents, reutilitzables i modulars.
Definir la primera funció
Fixeu-vos en el següent programa: llegeix la part numèrica d’un DNI (passat per argument), i mostra la lletra corresponent. Aquesta primera versió és la que es faria sense utilitzar funcions. L’anomenarem dni_inicial.py:
import sys LLETRES_DNI = 'TRWAGMYFPDXBNJZSQVHLCKE' dni = int(sys.argv[1]) #Càlcul de la lletra pos = dni % len(LLETRES_DNI) print(LLETRES_DNI[pos])
Aquesta és una nova versió del mateix programa, però en el que es defineix i s’utilitza una funció anomenada lletra_dni(). A aquesta versió l’anomenarem dni_funcio.py:
import sys def lletra_dni(num_dni): ''' Funció que rep un nombre que representa un dni i retorna la lletra corresponent Entrada: int (nombre que representa un dni) Sortida: str (caràcter corresponent a una lletra en majúscula) ''' LLETRES_DNI = 'TRWAGMYFPDXBNJZSQVHLCKE' pos = dni % len(LLETRES_DNI) return LLETRES_DNI[pos] #Programa Principal dni = int(sys.argv[1]) #Càlcul de la lletra print(lletra_dni(dni))
En la figura es mostra programa amb els números de línia.
Explicació dels nous conceptes:
- En la línia 3 es defineix la funció
lletra_dni. Per a definir una funció, s’utilitza la paraula claudefseguida del nom de la funció, parèntesis i els noms dels paràmetres que s’utilitzaran per a codificar-la. - Les línies 4 a 9 corresponen al docstring de la funció. El docstring és un comentari (i, per tant, és transparent per al processador) que dona informació sobre l’ús de la funció.
- En les línies 10 a 12 hi ha el codi de la funció.
- En la línia 12 apareix la paraula clau
return. L’objectiu de les funcions és transformar les dades d’entrada en un valor de sortida. Quan s’ha calculat aquest valor es retorna el control al programa, amb la paraulareturni el valor de retorn que ha d’utilitzar el programa que ha invocat la funció. - Les línies 14 a 17 són el codi del programa principal.
- En la línia 17 s’invoca la funció. Des del programa es passa com a argument el valor de la variable entera
dni. Aquest valor concret s’envia a la funció i se substitueix pel paràmetredni_numeric.
- La funció acaba quan es recupera la identació del codi principal. En el nostre exemple, la darrera línia de la funció seria la 12. Habitualment és on hi és el return.
Paràmetres i arguments
Quan ens referim a les entrades a una funció, de vegades ens hi referim com als arguments de la funció, i en altres ocasions com a paràmetres de la funció. Els dos conceptes estan molt lligats, però convé saber diferenciar-los, ja que és important que en comprengueu les diferències.
Paràmetre d’una funció: és un nom especificat en la definició de la funció que serveix com a marcador de posició per als valors que es passaran a la funció quan sigui cridada.
Argument d’una funció: Un argument d’una funció és el valor real que es passa a la funció quan es crida. Aquests valors reals es corresponen amb els paràmetres definits a la funció.
Paràmetres i arguments en el programa dni_funcio.py
Executeu el programa de la figura amb el valor d’entrada 12345678:
:~$ python3 dni_funcio.py 12345678 ZObtindreu que la lletra corresponent al DNI és la Z.
En aquest exemple, el paràmetre amb el qual s’ha definit la funció és num_dni. En la línia 3, s’ha utilitzat aquest nom per a definir el valor que entrarà a la funció. En tot el bloc de la definició, de les línies 3 a la 6, s’utilitza aquest paràmetre.
El concepte de paràmetre d’una funció és semblant al de la variable on es desen les dades d’entrada en un programa. En canvi, l’argument és el valor real amb el qual es fan els càlculs. En aquest cas, quan es crida la funció des del programa principal, se li envia el valor que conté la variable dni. La funció lletra_dni és cridada amb l’argument 12345678.
Les diferències entre els paràmetres i els arguments són:
Paràmetres:
- Els paràmetres són els noms de les variables que es defineixen en la signatura d’una funció o mètode.
- Són utilitzats per definir els requisits de la funció i per especificar quines dades es necessiten per dur a terme una certa operació.
- Els paràmetres es defineixen quan es declara la funció o el mètode i són les variables locals dins del cos de la funció.
- Aquests paràmetres serveixen com a placeholders per les dades que s’esperen rebre quan la funció o mètode sigui cridat.
Arguments:
- Els arguments són les dades reals que es passen a una funció o mètode quan aquest és cridat.
- Són les instàncies o valors concrets que es passen a la funció o mètode perquè aquest pugui operar sobre elles.
- Els arguments es proporcionen quan es crida la funció o el mètode, i s’han d’ajustar als paràmetres definits en la signatura de la funció o mètode.
- Quan es crida una funció o mètode, els arguments s’associen als paràmetres segons l’ordre en què són passats o mitjançant noms de paràmetres (en el cas de les funcions amb arguments que tenen nom).
Docstring d'una funció
El docstring d’una funció en Python és un tipus de comentari especial que es col·loca al principi de la definició de la funció per documentar i explicar què fa la funció, quins són els seus paràmetres i què retorna.
Els docstrings són útils per aclarir la funcionalitat d’una funció i proporcionar informació sobre com ha de ser utilitzada correctament. Són una part important de la documentació del codi i ajuden els altres programadors (o fins i tot a vosaltres mateixos en el futur) a entendre i utilitzar la funció de manera efectiva.
Un docstring s’escriu com una cadena de text multilínia entre tres cometes, i es col·loca just després de la definició de la funció, abans del cos de la funció.
Docstring de la funció predefinida abs()
La documentació en línia de Python us ofereix el docstring de les funcions predefinides. El docstring corresponent a la funció abs()obtingut amb help(abs) és el següent:
abs(x, /)
Return the absolute value of the argument.
Observeu que consta d’una explicació sobre què fa la funció, es determina la quantitat de paràmetres que té (1 paràmetre, que és x), i indica quin és el retorn.
Docstring de la funció lletra_dni()
En la figura s’ha declarat la funció lletra_dni amb el següent docstring:
def lletra_dni(num_dni): ''' Funció que rep un nombre que representa un dni i retorna la lletra corresponent Entrada: int (nombre que representa un dni) Sortida: str (caràcter corresponent a una lletra en majúscula) '''És un comentari que s’escriu just a sota de la declaració (i que pertany al bloc de la funció, o sigui que ha de tenir la mateixa indentació). S’escriu entre 3 cometes simples(') o 3 cometes dobles (“). Inclou una petita explicació i els tipus corresponents a les entrades i retorns de la funció.
El docstring és un comentari, de manera que és transparent per al processador. No és necessari perquè es pugui executar el codi. Però serà obligatori en les funcions que definiu vosaltres en aquest curs. Si el nom de la funció és molt descriptiu, l’explicació es pot obviar. Però serà imprescindible d’ara endavant que cada funció vagi sempre acompanyada del seu docstring on quedi clar quins tipus de dades espera rebre la funció i quin serà el seu tipus de retorn.
El fet que el Python no sigui un llenguatge tipat permet que de vegades es defineixin funcions o que s’utilitzin sense tenir en compte per a quines dades estan pensades. El docstring serà el primer pas que haureu de fer en el moment de determinar les funcions d’un programa. Aquesta reflexió us ajudarà a trobar la solució idònia per implementar a cada programa.
Retorn d'una funció
La clàusula return en Python s’utilitza dins del cos d’una funció per especificar quin valor o expressió ha de ser retornada quan la funció és cridada. És utilitzada per finalitzar l’execució de la funció i retornar el valor especificat al codi que ha cridat la funció.
L’objectiu principal d’una funció és crear una rutina o un procés, que sol finalitzar amb el càlcul d’un valor (tot i que ja sabeu que hi ha funcions que no retornen res, com la funció print()).
return s’utilitza dins del cos d’una funció per especificar el valor que la funció ha de retornar quan és cridada. Aquest valor pot ser qualsevol tipus de dada: nombres, cadenes, llistes, tuples, diccionaris, objectes, o fins i tot altres funcions.
Quan una funció arriba a la clàusula return, s’atura l’execució de la funció i el valor especificat després del return és retornat a la part del programa que ha cridat la funció. Això significa que la funció pot calcular o manipular dades i retornar el resultat perquè altres parts del programa l’utilitzi.
És important notar que una funció pot tenir més d’una clàusula return, però només es pot arribar a executar una d’elles per crida a la funció. Quan s’arriba a un return, la funció finalitza i retorna el valor especificat, saltant-se qualsevol línia de codi que pugui existir després del return.
Crida a una funció
Quan crideu una funció que heu definit en el mateix programa, heu de seguir el mateix procés que quan cridem funcions predefinides pel sistema, amb la diferència que abans cal haver definit la funció.
- Definició de la funció: primer, heu de definir la funció. Python permet definir funcions enmig del programam principal, però com que la llegibilitat és fonamental en aquest curs, cal que definiu les funcions abans del programa principal. La definició de la funció inclou el nom de la funció, els seus paràmetres (si n’hi ha), el docstring i el bloc de codi que defineix la funcionalitat de la funció.
- Crida a la funció: després de definir la funció, podeu cridar-la des d’altres parts del teu programa (inclòs dins d’una altra funció). Per cridar la funció, simplement escriviu el nom de la funció seguit dels parèntesis. Si la funció requereix arguments, introduïu-los dins dels parèntesis.
- Execució del codi de la funció: quan es crida la funció, el codi dins de la funció s’executarà amb els valors proporcionats com a arguments (si n’hi ha). La funció pot realitzar diverses accions i càlculs, i pot retornar un valor mitjançant la clàusula
return. - Utilització del valor de retorn (si n’hi ha): si la funció retorna un valor, aquest valor es pot utilitzar en altres parts del programa. Podeu emmagatzemar el valor de retorn en una variable o utilitzar-lo directament en altres càlculs o operacions.
Seguiment d'un programa amb funcions
Per acabar d’entendre el primer programa amb funcions, cal fer-ne un seguiment. Farem el seguiment del programa dni_funcio.py que podeu consultar a figura.
Recordeu que per a fer un seguiment s’ha d’escollir un cas concret i anar fent els càlculs que faria el processador, indicant com canvien els valors de les variables a cada instrucció. Quan el programa crida una funció, passa momentàniament el control a la funció. En el seguiment es representarà amb una taula a part (una funció és una subrutina; el procés de la funció és diferent del del programa). Quan s’ha obtingut el valor de retorn, se segueix amb el flux del programa. Les funcions no intervenen en el programa fins que no són cridades.
Un programa de Python (el programa principal) comença en la primera instrucció que es troba indentada més a l’esquerra (en la columna 1). En l’exemple que ens ocupa, el flux del programa principal s’inicia en la línia 15. Farem el cas en què per la línia d’ordres s’escriu la cadena '12345678' (recordeu que sys.argv és una llista de cadenes).
A la taula hi ha el seguiment del programa principal corresponent al programa dni_funcio.py que teniu a figura.
| Línia | DNI | Sortida |
|---|---|---|
| 15 | 12345678 | |
| 17 | ‘Z’ |
En la línia 15 es carrega el valor enter 12345678. Tot i que la funció sys.argv(1) és una cadena, el valor '12345678' de tipus str s’ha passat com a argument a la funció int(), que l’ha transformat en enter. Aquest valor enter s’ha desat a la variable dni.
En aquest exemple, num_dni és el paràmetre de la funció lletra_dni(): s’utilitza per a definir la funció. El valor 12345678 és l’argument amb el que es crida la funció lletra_dni(). En el programa principal, es referencia aquest valor a través de la variable de nom dni.
En la línia 17 s’invoca a la funció lletra_dni, definida en el mateix fitxer. En aquest punt, el programa principal es queda a l’espera i es passa el control a la funció, de la qual fem el seguiment en la taula.
| Línia | num_dni | LLETRES_DNI | pos | Valor de retorn |
|---|---|---|---|---|
| 3 | 12345678 | |||
| 10 | ‘TRWAGMYFPDXBNJZSQVHLCKE’ | |||
| 11 | 14 | |||
| 12 | ‘Z’ |
Fer un seguiment d’una funció és el mateix que fer el seguiment d’un programa, l’única diferència és que les dades, en comptes d’entrar per l’entrada estàndard (com amb input()), o per la línia d’ordres (com si s’utilitza sys.argv), entren com a arguments, amb un tipus i un valor concrets. Aquests valors es “carreguen” en els paràmetres, que actuen com una variable al llarg de la definició de la funció. Vegem-ho:
- En el nostre cas, el paràmetre
num_dnide la línia 3 pren el valor12345678, com a enter, que és el que li ha passat el programa principal en la línia 15 i que era el valor que tenia l’argumentdni. - En la línia 10 es crea la variable
LLETRES_DNI. - La línia 11 és una assignació a la variable
pos. Cal calcular l’operació de la dreta. Primer s’ha d’aplicar la funciólen()a la constant, que dona el resultat de 23 (hi ha 23 lletres). També s’ha de fer el càlcul12345678 % 23. El resultat d’aquesta operació és 14 i és el valor que pren la variablepos. - En la línia 12 es fa l’última operació i s’envia en valor de retorn. Primer es calcula quin és el caràcter que ocupa la catorzena posició (la lletra ‘Z’), i es retorna aquest valor al programa principal.
- En aquest moment cal anar a la taula i seguir allà on s’havia deixat: en la línia 17, en el punt en què s’havia cridat a la funció
lletra_dni(). Aquesta funció retorna el valor'Z'. Aquest valor es podria emmagatzemar per utilitzar més endavant, però s’ha optat per mostrar-lo per la sortida estàndard. - En la taula, la filera corresponent a la línia 19 ha de mostrar
'Z'com a valor de sortida del programa.
És molt recomanable que les variables que actuen com a paràmetres de les funcions tinguin noms diferents de les variables que representen als arguments. En cas contrari, es poden presentar problemes entre variables locals i globals.
Fixeu-vos que en aquest exemple, l’argument amb el qual es crida la funció es representa amb la variable dni, i, en canvi, el nom del paràmetre és num_dni. Eviteu costi el que costi que es diguin igual.
El test-driver
Un test-driver és un petit programa que testa la funció. És l’equivalent al joc de proves pel que respecta a un programa.
Una funció no és més que una part d’un programa i, per tant, s’han de codificar seguint la mateixa metodologia que s’utilitza per a un programa. Recordeu quins són els passos:
- Entendre l’enunciat.
- Fer un o diversos exemples a mà.
- Fer el joc de proves i les especificacions d’entrada.
- Fer el primer refinament.
- Fer els refinaments successius.
- Codificar.
- Executar i comprovar el joc de proves.
- Fer el seguiment dels fragments que siguin necessaris per identificar i resoldre errors.
Joc de proves i especificacions d'entrada
Les especificacions d’entrada, en el cas de les funcions, es contemplen en el docstring. Quan es defineix una funció, el primer que s’ha de fer sempre és pensar en un nom adequat i definir el docstring de la funció. De la mateixa manera, abans de codificar és necessari definir un joc de proves, i abans de codificar, també caldrà fer el refinament en l’editor de text. El que modificarem una mica amb les funcions serà la verificació del codi amb l’execució del joc de proves, creant un test-driver per comprovar la funció.
El test-driver, o controlador de proves, és una estratègia de desenvolupament de programari utilitzada per provar funcions, mètodes o components de codi de manera sistemàtica. En l’àmbit de les funcions, el test-driver és un petit programa que crida la funció que volem provar amb diversos conjunts d’entrades i comprova si les sortides són les esperades (verifica el joc de proves).
Abans d’utilitzar una funció en un programa cal fer-ne i comprovar el test-driver. A banda de verificar la funció, servirà com a documentació de la mateixa: el joc de proves quedarà documentat amb el test-driver de la funció.
Test-driver de la funció lletra_dni()
En el programa dni_funcio.py (figura) la funció lletra_dni s’utilitzava per a calcular la lletra d’un DNI entrat per la línia d’ordres. El programa principal és molt senzillet, ja que només recull el valor de sys.argv i el transforma a enter abans d’enviar-lo com a argument de la funció.
Però un test-driver ha de ser un programa encarA més senzill. De moment, els test-driver de les funcions que definirem es limitaran a cridar la funció amb els valors del seu joc de proves i mostrar els resultats, per poder comparar-los amb el joc de proves i verificar que són els esperats.
A continuació teniu el codi d’un fitxer test-driver, que conté la funció lletra_dni, i vàries crides a aquesta funció per tal de provar que funciona com esperem:
def lletra_dni(num_dni): ''' Funció que rep un nombre que representa un dni i retorna la lletra corresponent Entrada: int (nombre que representa un dni) Sortida: str (caràcter corresponent a una lletra en majúscula) ''' LLETRES_DNI = 'TRWAGMYFPDXBNJZSQVHLCKE' pos = dni % len(LLETRES_DNI) return LLETRES_DNI[pos] # Test-driver lletra_dni(12345678) lletra_dni(23456780)
Avantatges de programar amb funcions
Després d’estudiar aquest exemple és molt possible que us pregunteu què guanyeu definint funcions per programar. Podeu pensar, inclús, que no fan més que embolicar i enterbolir el programa.
A continuació teniu un llistat dels avantatges de programar de manera modular (és a dir, separant les coses en mòduls). Una funció no és més que una part del programa que s’ha modulat i s’ha programat de manera independent.
Llegibilitat
En l’exemple del càlcul del DNI, s’ha separat del programa el codi del càlcul. Permet centrar l’atenció en la globalitat sense perdre’s en els detalls. Aquest programa és molt senzill, però al llarg de la unitat formativa anireu creant programes, amb moltes línies de codi. Apartar les instruccions que defineixen les funcions us ajudarà molt a fer-vos una idea del funcionament d’un programa només amb una mirada ràpida.
Evitar repeticions
Quan es repeteixen un seguit d’instruccions que tenen un objectiu comú en un programa podem evitar reescriure tot el codi si definim una funció. Si en un altre punt del programa fos necessari calcular la lletra d’un altre DNI, no caldria repetir el codi. Només cridant la funció amb un nou argument ja n’hi hauria prou.
Reusabilitat
Les funcions permeten reutilitzar codi de manera molt més fàcil que com ho fèiem fins ara (retallant i enganxant). Però calia anar amb compte amb els noms de les variables, la indentació i tenir molt clar quina era la primera i última línia que ens calia retallar. Les funcions són fragments de codi independents i molt més fàcils de reutilitzar.
El càlcul de la lletra es podria utilitzar, per exemple, en una funció que calculi si un DNI és correcte o no. Aquest seria un cas en què una funció és cridada per una altra funció, on fa una feina molt específica.
Separar funcionalitats
Si per una estranya raó es decidís modificar la cadena del càlcul del DNI, només caldria fer-ho en la funció on es defineix la lletra, deixant intacte el codi de tots els programes que l’utilitzen. Separar les funcionalitats d’un programa permet que si cal modificar o millorar una part es pugui fer sense afectar als programes o aplicacions que l’utilitzen.
Modulació o construir per mòduls
Modular és dividir un programa en diversos mòduls o blocs. Les funcions ens permeten modular i així organitzar el codi en parts més senzilles de programar. Fer una funció que calculi la lletra d’un DNI i després utilitzar-la en un programa (o millor en una altra funció) que validi si el DNI és o no correcte, és més senzill que pensar un programa que ho faci tot de cop.
Encapsulament
En últim lloc, tot el codi que utilitzem dins d’una funció està encapsulat. Les funcions només s’executen si són cridades des del programa principal. Processen les seves instruccions i retornen un resultat a la part del codi del programa principal que les ha invocat. Els valors d’aquestes variables internes són locals i no entren en conflicte amb la resta del programa, ni que tinguin els mateixos noms.




