16: DEFINIZIONE DI FUNZIONI

SCRIVIAMO LE NOSTRE FUNZIONI

Le funzioni di Python ci sono ormai familiari, ma in realtà sappiamo ancora molto poco su di esse. Infatti tutti i linguaggi di programmazione, oltre a fornire un certo numero di funzioni predefinite, permettono all'utente di creare da sè le proprie funzioni. Questo significa che è possibile scrivere delle righe di codice separate dal programma principale, dare loro un nome, ed eseguirle nel resto del programma con una chiamata del tutto uguale a quelle che già conosciamo. Per creare una tale funzione sono quindi necessari alcuni passi:

Iniziamo con questo semplice esempio:

NUOVO PROGRAMMA: prova_funzioni.py

def firma():
    print("\tFirmato: Il grande Peppe")
    print("\tImperatore galattico")
    print("\tPadrone dell'Universo")
   
# L'esecuzione inizia da qui!!!
print ("Tutti i professori devono mettermi 10")
firma()
print ("Tutti i compagni devono passarmi i compiti")
firma()
print ("Il bar della scuola deve farmi i cappuccini gratis")
firma()

L'istruzione def definisce una funzione creata dal programmatore: è seguita dal nome della funzione con le parentesi tonde e i due punti. Il blocco di istruzioni (indentate) che segue costituisce il corpo della funzione: queste istruzioni saranno eseguite ogni volta che la funzione verrà chiamata (dal programma principale o da un'altra funzione).

Notate che dopo la def e le righe indentate ho lasciato una riga bianca, per sottolineare che le prime quattro righe non fanno parte del programma principale. Proviamo ad eseguire il programma: l'output che otterremo sarà questo:


Tutti i professori devono mettermi 10
	Firmato: Il grande Peppe
	Imperatore galattico
	Padrone dell'Universo
Tutti i compagni devono passarmi i compiti
	Firmato: Il grande Peppe
	Imperatore galattico
	Padrone dell'Universo
Il bar della scuola deve farmi i cappuccini gratis
	Firmato: Il grande Peppe
	Imperatore galattico
	Padrone dell'Universo

E' importante notare che quando si lancia il programma la definizione di una funzione (cioè il blocco dopo il def) non viene eseguita. Python trova per prima cosa il def : memorizza il nome della funzione firma() e salta tutte le righe indentate. Il programma principale comincia dalla sesta riga: per tre volte scrive una frase e poi chiama la funzione firma(), e solo allora Python salta alla funzione ed esegue le righe indentate. Le funzioni definite dall'utente costituiscono quindi dei "sottoprogrammi" che vengono chiamati dal programma principale con l'usuale sintassi per le chiamate. Per questo si usa separare le definizioni di funzioni dal resto del programma con una linea bianca.

Provate ad eseguire il programma passo passo con il debugger: se usate il tasto Step Into lo vedrete iniziare dalla sesta riga e "saltare" per tre volte alla funzione. Usando invece lo Step Over ad ogni chiamata il debugger eseguirà tutta la funzione in una volta sola continuando con la riga successiva.

Il principale vantaggio dell'uso delle funzioni si ha quando in un programma bisogna ripetere più volte le stesse istruzioni. Certamente avremmo potuto evitare di definire la funzione firma(), scrivendo le righe direttamente nel programma principale, ma avremmo avuto i seguenti svantaggi:

FUNZIONI CON ARGOMENTI

Come sappiamo le funzioni di Python possono avere degli argomenti. Questo è possibile anche per le funzioni definite nel programma:

NUOVO PROGRAMMA: saluti.py

def saluta(nome):
    print ("Ciao", nome + "!")   # nome + "!" non inserisce lo spazio tra il nome e "!"
    
# qui inizia il programma principale
saluta("Peppe")
saluta("Ciccio")
n = input("Chi vuoi salutare? ")
saluta(n)

Per usare degli argomenti nella funzione dobbiamo scrivere, nella definizione, il loro nome tra parentesi tonde, separati da virgole; nelle funzioni definite dal programmatore si usa di solito il termine parametri come sinonimo di argomenti.

Ogni volta che chiameremo la funzione dal programma principale Python controllerà che nella chiamata ci sia lo stesso numero di parametri della definizione (altrimenti si otterrà un TypeError), e copierà i parametri che abbiamo fornito nella chiamata (parametri effettivi) nei rispettivi parametri della definizione (parametri formali) secondo il loro ordine. A questo punto, nel corpo della funzione, i parametri diventeranno delle variabili in piena regola, che potremo leggere e scrivere.

Ad esempio, nel programma precedente, la prima volta che chiamiamo la funzione Python copierà la stringa "Peppe" nel parametro nome, poi copierà "Ciccio", mentre la terza volta copierà il contenuto della variabile n (in Italiano si usa il termine "passare il parametro alla funzione").

Nel corpo della funzione i parametri sono quindi delle variabili come tutte le altre, il cui valore viene però impostato nella chiamata anzichè attraverso un'assegnazione. Inoltre, come di consueto, potremo creare nella nostra funzione altre variabili, semplicemente assegnando loro un valore. Tutto questo pone un fondamentale problema, che spesso disorienta i principianti: che rapporto c'è tra le variabili nel corpo di una funzione e quelle fuori di esso? Cosa succederebbe se usassi lo stesso nome di variabile sia dentro che fuori la funzione? Questo problema è talmente importante (e complicato!) che dedicheremo ad esso tutta la prossima lezione; per il momento, provvisoriamente, possiamo risolverlo usando per le nostre variabili tutti nomi diversi.

Il prossimo esempio mostra come, nella chiamata, possiamo scrivere come parametro una costante (numero o stringa), una variabile oppure un'intera espressione, che sarà valutata e passata alla funzione (tutte cose che già eravamo abituati a fare con le funzioni predefinite di Python, ma che, lo ribadisco, possiamo fare anche con le funzioni definite da noi).


def scrividoppio(a):      # a e' il parametro formale
    print("Il doppio di", a, "e'", 2*a)
	
# qui inizia il programma principale
x = 3
scrividoppio(5)           # scrive: "Il doppio di 5 e' 10
scrividoppio(x)           # scrive: "Il doppio di 3 e' 6
scrividoppio(2 * x + 1)   # scrive: "Il doppio di 7 e' 14

Vediamo un altro esempio con più parametri:

NUOVO PROGRAMMA: compra.py

def compra (cosa, sn, quanto, prezzo):
    print ("Avete", cosa + "?")
    if sn == "n":
        print ("Spiacente, non ne abbiamo")
    else:
        print ("Certamente! Quanto ne vuole?")
        print (quanto, "etti")
        print ("sono", prezzo * quanto, "euro")

compra("gorgonzola", "s", 2, 1.5)
compra("prosciutto", "s", 2, 3.5)
compra("corni di rinoceronte", "n", 1, 1)

Ricordate che la chiamata deve avere esattamente lo stesso numero di parametri della definizione, e che i parametri saranno sostituiti in base al loro ordine (il primo diventera cosa, il secondo sn ecc.). Cercate di analizzare bene la funzione (eventualmente con il debugger) e di capire a cosa serve ogni parametro. Provate a modificare i parametri nelle chiamate osservando cosa succede.

ESERCIZIO 16.1: Scrivete una funzione stampa_quadrato(x) che prenda in ingresso un parametro x e stampi "Il quadrato di x e' yyyyy". Chiamatela più volte nel programma principale con numeri diversi.

ESERCIZIO 16.2: Riprendiamo il programma saluti.py e modifichiamolo cosi:

def saluta(nome):
    print ("Ciao", nome + "!")

saluta("Peppe")
saluta("Rosina")
Ora vogliamo modificare la funzione saluta() in modo che risponda in questo modo:
Ciao Peppe!
Sei il mio migliore amico
oppure:
Ciao Rosina!
Sei la mia migliore amica
Abbiamo bisogno di un altro parametro nella funzione che ci dica se dobbiamo salutare un maschio o una femmina: questo parametro potrebbe essere una stringa che vale "m" o "f". Quindi le chiamate andranno modificate così:

saluta("Peppe", "m")
saluta("Rosina", "f")
Siete capaci di modificare il corpo della funzione?
SOLUZIONI

Soffermiamoci infine per riassumere cosa succede esattamente quando Python incontra una funzione:

L'ISTRUZIONE return

Infine vediamo come è possibile fare in modo che una funzione da noi definita restituisca un valore al programma principale.

NUOVO PROGRAMMA: prova_return.py

def cubo(x):
    cubo = x ** 3
    return cubo
	
a = float(input("Scrivi un numero "))
print("Il cubo di", a, "e'", cubo(a)) 

L'istruzione return, che si può scrivere solo nel corpo di una funzione, può essere seguita o no da un'espressione.

La seconda forma del return rende le funzioni simili a quelle della Matematica: il programma passa loro uno o più argomenti ed esse restituiscono un dato calcolato in base agli argomenti ricevuti. Quindi il risultato di una funzione può essere usato in qualunque espressione (come abbiamo fatto per le funzioni predefinite di Python) oppure assegnato ad una variabile. Modifichiamo ancora il programma così:


def cubo(x):
    return x ** 3    # ancora piu' sintetico, usando un'espressione

def minimo(x, y):
    if x <= y:
        return x
    else:
        return y

a = float(input("Scrivi un numero "))
print("Il cubo di", a, "e'", cubo(a)) 
b = float(input("Adesso un altro numero "))
print("Il minimo tra", a, "e", b, "e'", minimo(a, b)) 
NUOVO PROGRAMMA: borse_studio.py
ESERCIZIO 16.3: Riprendere l'Esercizio 3.3 scrivendo una funzione che prenda in ingresso due parametri media e reddito e restituisca True se lo studente può avere la borsa di studio e False altrimenti. Nel programma principale copiate la seguente lista, che contiene nell'ordine il cognome, la media ed il reddito degli studenti che hanno fatto domanda per la borsa di studio:

studenti = [ ["Alberti", 8.5, 29500], ["Bongini", 7.9, 28000], ["Castroni", 8.6, 36500],
["De Lallis", 8.2, 27500], ["Esterini", 8.5, 31000], ["Folletti", 7.7, 27000],
["Giuliani", 9.1, 28000], ["Lulli", 8.0, 30500], ["Milanesi", 8.2, 25500], ["Nonnini", 8.6, 34000],
["Pampanato", 7.9, 26000], ["Rossi", 8.2, 31000], ["Santi", 8.1, 28000], ["Tontini", 7.8, 25000] ]

Ora scrivete un ciclo for che scorra la lista stampando solo i nomi degli studenti che possono ottenere la borsa di studio. SUGGERIMENTO: ogni elemento della lista è a sua volta una lista, quindi dovete considerare che, se fate variare l'iteratore nell'insieme degli indici, dovete applicare le tecniche imparate nell'ESERCIZIO 15.11: il cognome del primo studente sarà in studenti[0][0], la sua media in studenti[0][1] e il suo reddito in studenti[0][2]...
SOLUZIONI

Riprendiamo ora il nostro vecchio programma divisibilita.py e trasformiamolo in una funzione


def divisibilita(n): 
    for k in range(2, n // 2 + 1):
        if n % k == 0:      # se n e' divisibilr per k ...
            return k        # restituisce k (< n)
    return n                # altrimenti restituisce n ( n e' primo)

questa funzione restituisce il più piccolo divisore di n (diverso da 1); se n è un numero primo restituisce n stesso. Possiamo utilizzarla per fare qualche esercizio di aritmetica:

ESERCIZIO 16.4 numeri_primi.py: usate la funzione divisibilita per stampare una tavola dei numeri primi da 1 a 1000 (un numero n è primo se divisibilita(n) == n)

ESERCIZIO 16.5 PROGRAMMA PASSO PASSO: scomposizione.py
Scriviamo ora un programma che scomponga un numero num in fattori primi. Il procedimento è uguale all'algoritmo con carta e penna: dato num cerchiamo div, il più piccolo fattore primo di num, dopodichè dividiamo num per div ed iteriamo il procedimento fino a quando non otteniamo 1.
  1. Create una lista fattori inizialmente vuota
  2. Chiedete all'utente di immettere il numero intero num
  3. Ora entriamo in un ciclo while, da ripetere finchè num è maggiore di 1
  4. Usando la funzione divisibilita() troviamo div
  5. Aggiungiamo div alla lista fattori
  6. Ora dividiamo num per div e riassegnamo il risultato a num (qui finisce il corpo del ciclo). ATTENZIONE: è necessario usare la divisione intera // perchè la divisione normale / trasformerebbe il numero in un float: questo è uno dei rari casi nei quali dobbiamo fare attenzione alla distinzione tra interi e float
  7. Alla fine stampiamo la lista fattori
SOLUZIONI

Fine della lezione