Referències.

Referències:

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 código, en comptes d'haver-lo de repetir.

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ón 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ò és força habitual.

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))

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

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))

Programació Funcional en Python

El concepte / paradigma de la programació funcional fa referència a un estil de programació que utilitza les funcions com a unitat bàsica de codi.

Hi ha des de llenguatges purament funcionals com ara Haskell o Lisp, fins a llenguatges multiparadigma com Python, així que no és tan fàcil separar els llenguatges que suporten la programació funcional.

Perquè un llenguatge permeti la programació funcional, ha de tractar les funcions com a ciutadans de primera classe. És el que passa amb Python; les funcions són objectes, igual que els strings, els números i les llistes.

Molts llenguatges donen suport a la programació funcional; com Javascript, C#, PHP i Java.

Anem a veure els conceptes més importants per entendre aquest paradigma.

Funcions pures.

Aquest paradigma tracta d’emprar el màxim possible de funcions pures (almenys una segur que hi ha)

Funcions 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.

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}")

Pros:

  • Són les funcions més reutilitzables.
  • Més testejables.

És important que les funcions no tinguin gaires línies en general.

Encara que no usem Programació Funcional, val la pena usar funcions pures per minimitzar errors.

Funcions d'ordre superior.

A partir d’una funció pura i una col·lecció (llista, diccionari …) podem aplicar funcions d'ordre dintre dels seus elements.

Ens estalvien usar bucles, i a més a més són més elegants.

Les més habituals són:

  • map, per editar tots els vaqlors
  • filter, per filtrar un subconjunt.
  • reduce, va bé per agrupar valors, però s'utilitza menys.
  • zip, s'utilitza si volem treballar amb tuples.

Més endavant veurem que altres llibreries de dades, com Pandas, accepten funcions pures per recalcular les seves variables.

Ara toca veure amb exemples les possibilitats que ens ofereixen :)

Map.

Crea una llista de números i una funció per multiplicar per 3 un número; per tal que es mostrin per pantalla tots els números de la llista multiplicats per 3.

def mult3 (num: float) -> float:
    return num * 3

# range genera sequències de números
# range (num_ini, num_fin, step)
llistaNums: float = list(range(10,40,2))
print("Llista números original.")
print(llistaNums)
print("Llista números multiplicats per 3.")
llistaNumsPer3 = list(map(mult3,llistaNums))
print(llistaNumsPer3)

Filter.

Crea una llista de números (pex de notes d'alumnat) i una funció per a comprovar si el número és major o igual a 5, i fes que es mostrin per pantalla únicament els números de la llista majors o iguals que 5. Finalment, calcula el percentatge de números filtrats (els >=5) arrodonit a 2 decimals.

def greaterOrEqual5 (num: float) -> bool:
    return num >= 5

llistaNotes: float = [8,5,6.2,4.2,10,6.8,3.4,7.9,9.3,8,2.4,9.7,7.6]

print("Llista notes original.")
print(llistaNotes)

print("Llista notes majors o iguals a 5.")
llistaNotesMajorsIguals5 = list(filter(greaterOrEqual5,llistaNotes))
print(llistaNotesMajorsIguals5)

print("Percentatge aprovats.")
# Dividim la longitud de les notes >5 respecte el total de notes 
# La funció len ens permet veure el número d'elements de les llistes. 
percAprov: int = len(llistaNotesMajorsIguals5) / len(llistaNotes)

## Per arrodonir a 2 decimals usem funció round.
print( str( round(percAprov,4)*100) + ' %')

Fixa't on està la màgia:

list(filter(greaterOrEqual5,llistaNotes))
  • filter la funció d'ordre superior

  • greaterOrEqual5 nom de la funció

  • llistaNotes nom de la llista on volem aplicar la funció.

  • list per comoditat el resultat el retornem com a llista.

Podem realitzar una implementació més genèrica i flexible:

def greaterOrEqual5 (num: float) -> bool:
    return num >= 5

def positiveNums (num: float) -> bool:
    return num >= 0

llistaNums: float = [-4.5,8,5,6.2,4.2,10,6.8,3.4,7.9,9.3,8,2.4,9.7,-12.3]

def filterNums(lista, filter):
    result = []
    for i in lista:
        if filter(i):
            result.append(i)

    return result

print("---")
print(filterNums(llistaNums, greaterOrEqual5))
print(filterNums(llistaNums, positiveNums))

Exercici Filter. Crea una llista de números (pex de notes d'alumnat) i una funció per a comprovar si el número és major o igual a 5, i fes que es mostrin per pantalla únicament els números de la llista majors o iguals que 5. Finalment, calcula el percentatge de números filtrats (els >=5) arrodonit a 2 decimals.

Exemple de llista de notes: llistaNotes: list[float] = [8,5,6.2,4.2,10,6.8,3.4,7.9,9.3,8,2.4,9.7,7.6]

def greaterOrEqual5 (num: float) -> bool:
    return num >= 5

llistaNotes: list[float] = [8,5,6.2,4.2,10,6.8,3.4,7.9,9.3,8,2.4,9.7,7.6]

print("Llista notes original.")
print(llistaNotes)

print("Llista notes majors o iguals a 5.")
llistaNotesMajorsIguals5 = list(filter(greaterOrEqual5,llistaNotes))

print(llistaNotesMajorsIguals5)

print("Percentatge aprovats.")
# Dividim la longitud de les notes >5 respecte el total de notes 
# La funció len ens permet veure el número d'elements de les llistes. 
percAprov: int = len(llistaNotesMajorsIguals5) / len(llistaNotes)

## Per arrodonir a 2 decimals usem funció round.
print( str( round(percAprov,4)*100) + ' %')

Resultat: Llista notes original. [8, 5, 6.2, 4.2, 10, 6.8, 3.4, 7.9, 9.3, 8, 2.4, 9.7, 7.6] Llista notes majors o iguals a 5. [8, 5, 6.2, 10, 6.8, 7.9, 9.3, 8, 9.7, 7.6] Percentatge aprovats. 76.92 %

zip.

Tenim una llista de noms de països i una altra amb la seva població. Volem que es crei una llista hi hagi una tupla amb el nom i població de cada país. Així podrem iterar tota la informació d’un sol cop.

paises = ["China", "India", "Estados Unidos", "Indonesia", "Vietnam"]
poblaciones = [1391, 1364, 327, 264]
list(zip(paises, poblaciones))
# [('China', 1391), ('India', 1364), ('Estados Unidos', 327), ('Indonesia', 264)]
for pais, poblacion in zip(paises, poblaciones):
   print("{}: {} millones de habitantes.".format(pais, poblacion))

# També serveix per crear diccionaris.
dict1 = dict(zip(paises, poblaciones))
print(dict1)

Repte: Com s’implementaria el zip de Python a mà ?

l1 = [1,2,3,4]
l2 = ["1","2","3"]

def zip(lis1, lis2):
    result = []
   
    ## Si el tamany de les llistes és diferent
    ## fem que tinguin el mateix tamany
    l = len(lis1)
    if len(lis2) < l:
        l = len(lis2)

    for num in range(l):
        # Fusionem les 2 llistes en una tupla ( )
        result.append((lis1[num],lis2[num]))
        # Si volguessim un diccionari en comptes d'una tupla
        # result.append({lis1[num],lis2[num]})

    return result

print(zip(l1,l2))

Reduce.

Tenim una llista de números, i volem calcular el producte de tots els números, en un únic valor

from functools import reduce

producto = reduce((lambda x, y: x * y), [1, 2, 3, 4])
# Salida: 24

En aquest exemple veiem el concepte de funció anònima o Lambda.

lambda x, y: x * y

Funcions lambda (anònimes)

En ocasions no ens interessa tenir una funció creada per un sol cop que volem usar el codi. Per exemple, si únicament la volem per a fer map, filter... ens podem estalviar crear una funció amb nom i declarar-la com a anònima amb la paraula lambda.

Així, també millorem el rendiment.

Veiem un exemple senzill de funció lambda aplicat a la funció map:

# Lambda. Llista números multiplicats per 5.")
print(list(map(lambda x: x * 5,[3, 4, 8, 10])))
# Retorna: 
# [15, 20, 40, 50]