Les funcions son blocs de codi que es poden reutilitzar en diverses parts del codi.

Funcions

Una manera molt útil d'organitzar el fluxe d'execució del programa és agrupant un tros de codi en una funció per tal que el pogueum cridar en diferents parts del codi sense haver-lo de repetir.

Python disposa de diverses funcions predefinides, com el print, el len() ...

Ara aprendrem a crear les nostres propies funcions.

En qualsevol projecte informàtic rellevant és fonamental usar funcions i separar-les en diversos fitxers i/o classes (que ho veureu una mica més endavant)

Una funció en Python utilitza el mateix concepte que una funció matemàtica.

La capçalera està composta per:

  • La paraula reservada def
  • el nom de la funció
  • els paràmetres d'entrada entre parèntesis ()
  • i al final :

El cos de la funció té les instruccions que volem i si ho volem la paraula return i una variable o estructura.

No estem obligats a aplicar cap return, però ens interessa usar un return per a què la funció sigui pura.

Potser et preguntaràs si falten els paràmetres de sortida a la capçalera. No calen.

Anem a veure un parell d'exemples molt simples:

potencia(x, y) = x ^ y

def potencia(num, pot):
  return num ** pot

print("3 ^ 2 = ", potencia(3,2))
print("2 ^ 20 = ", potencia(2,20))

Per provar que realment funciona apliquem uns testos amb assert

assert potencia(3,2) == 9
assert potencia(3,2) == 1048576

cognom1_cognom2_nom(name, surname1, surname2="")

És una funció que mostra un nom i 2 cognoms en l'ordre cognom1 cognom2, nom

Fixa't que hem definit el paràmetre surname2 com a opcional, si l'usuari no posa res a surname2 s'omple automàticament amb un espai en blanc.

def cognom1_cognom2_nom(name, surname1, surname2=""):
    if surname2:
        return f"{surname1} {surname2}, {name}"
    else:
        return f"{surname1}, {name}"

print(cognom1_cognom2_nom("Ana","Santos","Oliveira"))
print(cognom1_cognom2_nom("Haruto","Sato"))

Funcions dins de funcions.

Per tal de descomposar un problema complexe en d'altres més petits i senzills podem cridar una funció dins d'una altra funció si ens cal, com es pot veure a l'exemple.

def calculate_area(length, width):
    return length * width

def calculate_volume(length, width, height):
    base_area = calculate_area(length, width)
    return base_area * height

length = 5
width = 3
height = 10

print(f"Area: {calculate_area(length, width)}")  # Output: Area: 15
print(f"Volume: {calculate_volume(length, width, height)}")  # Output: Volume: 150

Paràmetres opcionals.

En moltes ocasions, ens interessa simplificar la crida de les funcions, fent que alguns paràmetres tinguin un valor predeterminat, i que per tant no calgui omplir-los si tenen aquest valor predeterminat

És tant fàcil com ficar el valor predeterminat al costat del paràmetre:

def greet(name="stranger"):
    return "Hello, " + name + "!"

Anem a provar-lo:

assert greet() == "Hello, stranger!"
assert greet(name="Miquel") == "Hello, Miquel!"

Activitat.

1.- Crea un mètode per calcular el preu d'un producte. Tindrà 3 paràmetres: el preu, l'IVA (opcional), que per defecte serà el 21% i el descompte(opcional), que per defecte serà 0.

def preu_final(preu_total, descompte=0.0, iva=0.21):
  """
     calcular el preu d'un producte. Tindrà 3 paràmetres: 
     - preu (obligatori)
     - descompte(opcional), que per defecte serà 0.0
     - IVA en decimals(opcional) que per defecte serà el 0.21 
  """
  return preu_total - preu_total * descompte + preu_total * iva

# print(f"{preu_final(100)}")
assert preu_final(100) == 121.0
assert preu_final(100,0.2) == 101.0
assert preu_final(100,0.1,0.04) == 94.0

Docstring.

Per comentar una funció usem aquesta notació; abans del cos.

def foo():
    """A multi-line
    docstring.
    """
    # sentences

Type Hinting

Des de fa uns anys s'utilitza el Type Hinting en Python, per informar als programadors els tipus de dades de cara variable, paràmetres i valors de retorn.

És important que us acostumeu a veure-ho i probablement us ho demanin com a bona pràctica d'estil de codificació.

La propera funció la definirem amb aquests Type Hinting; van entre 2 punts en variables i arguments, i tenen una fletxeta en els paràmetres de sortida.

def potencia(num: float, pot: float) -> float:
  return num ** pot

print("2 ^ 20 = ", potencia(2,20))
print("2 ^ 20 = ", potencia(4.5,3))

Per aprofundir en l'ús de funcions, segueix llegint:

  • https://www.geeksforgeeks.org/python-functions/

Funcions pures.

Per tal de crear funcions senzilles d'entendres i de provar automàticament (per exemple, amb Pytest) han de complir el necessari per a ser pures:

  • Sol llegeix els seus paràmetres d'entrada
  • Sol escriu els seus paràmetres de sortida
  • Pels mateixos paràmetres d'entrada sempre retorna els mateixos paràmetres de sortida.
  • No tenen efectes colaterals fora de la funció.

Exemple funció pura:

def mult2(i: float) -> float: 
    return i*2

# let's test
assert mult2(4) == 8

Només podrem cridar funcions dins d’altres funcions sempre i quan siguin pures. I només podem testejar funcions pures.

Exemple funció impura.

name = 'John'
def greetings_from_outside():
  name = 'aaaa',name
  return(f"Greetings from {name}")

És impura perquè està escrivint la sortida amb variables de fora de la funció, utilitza una `variable global name`. 

La podem arreglar:

```py
def greetings_from_outside(name: str):
  return(f"Greetings from {name}")

Una altra recomanació és que les funcions no tinguin gaires línies en general.


Activitats

1.- L'ADN és pot representar amb un str, i les bases amb els caràcters "A", "G", "C" i "T".

Crea una funció que calculi l'aparició d'una de les bases:

def adn_count_base(adn, base):
    """Compta el número d'aparicions de la base"""
    counter = 0
    for x in adn:
        if x == base:
            counter += 1

    return counter

Crea una funció que calculi el porcentatge GC (el d'aparició de G i C):

def adn_percent_gc(adn):
    """Retorna el percentatge GC"""
    counter = 0
    for x in adn:
        if x == "G" or x == "C":
            counter += 1
    
    percent = round(counter / len(adn), 4)

    return percent