Python: Funzioni

Le funzioni sono blocchi di codice a cui associamo un nome.

Per definire una nuova funzione, scrivo:

def nome_funzione(argomento_1, argomento_2, ...):

    # qui metto il codice che usa argomento_1, argomento_2,
    # etc. per calcolare il valore della variabile risultato

    return risultato

Una volta definita la funzione, la posso chiamare/invocare dal resto del codice, cosi’:

valore_ritornato = nome_funzione(valore_1, valore_2, ...)

# qui uso valore_ritornato

Esempio. Definisco una funzione che prende due argomenti (che chiamo arbitrariamente numero1 e numero2) e ne stampa la somma:

def stampa_somma(numero1, numero2):
    print "la somma e'", numero1 + numero2

che uso cosi’:

# stampa: la somma e' 10
stampa_somma(4, 6)

# stampa: la somma e' 17
stampa_somma(5, 12)

# stampa: la somma e' 20
stampa_somma(19, 1)

Il codice qui sopra e’ del tutto equivalente a questo:

numero1 = 4
numero2 = 6
print "la somma e'", numero1 + numero2

numero1 = 5
numero2 = 12
print "la somma e'", numero1 + numero2

numero1 = 19
numero2 = 1
print "la somma e'", numero1 + numero2

Avvertimento

In stampa_somma() non c’e’ un return. Quando manca il return, il risultato della funzione e’ sempre None:

risultato = stampa_somma(19, 1) # stampa: la somma e' 20

print risultato                 # stampa: None

Esempio. Riscrivo la funzione stampa_somma(), che stampava la somma dei suoi argomenti, in modo che invece restituisca (con return) la somma come risultato:

def calcola_somma(numero1, numero2):
    return numero1 + numero2

Quando la chiamo succede questo:

# non stampa niente
calcola_somma(4, 6)

# non stampa niente
calcola_somma(5, 12)

# non stampa niente
calcola_somma(19, 1)

Perche’? Proviamo a riscrivere quest’ultimo codice per esteso:

numero1 = 4
numero2 = 6
numero1 + numero2

numero1 = 5
numero2 = 12
numero1 + numero2

numero1 = 19
numero2 = 1
numero1 + numero2

Qui e’ vero che effettivamente calcolo le varie somme, ma e’ anche vero che non le stampo mai: non c’e’ nessun print!

Quello che devo fare e’ mettere il risultato di calcola_somma() in una variabile, e poi stamparlo a parte, cosi’:

# non stampa niente
risultato = calcola_somma(19, 1)

# stampa 20
print risultato

(Oppure, abbreviando: print calcola_somma(19, 1)). Scritto per esteso, questo codice e’ equivalente a:

numero1 = 19
numero2 = 1
risultato = numero1 + numero2
print risultato

Ora tutto torna: faccio la somma e ne stampo il risultato.


Avvertimento

Il codice “contenuto” in una funzione non fa niente finche’ la funzione non viene chiamata!

Se scrivo un modulo esempio.py con questo codice:

def funzione():
    print "sto eseguendo la funzione!"

e lo eseguo:

python esempio.py

Python non stampera’ niente, perche’ la funzione non viene mai chiamata. Se voglio che venga eseguita, devo modificare il modulo esempio.py cosi’:

def funzione():
    print "sto eseguendo la funzione!"

funzione()  # <-- qui chiamo la funzione

Quano lo eseguo:

python esempio.py

Python trova la chiamata alla funzione (l’ultima riga del modulo) ed esegue la funzione: di conseguenza, stampera’:

sto eseguendo la funzione!

Esempio. Creo una funzione fattoriale() che prende un intero n e ne calcola il fattoriale n!, definito cosi’:

n! = 1 \times 2 \times 3 \times \ldots (n - 2) \times (n - 1) \times n

So farlo senza una funzione? Certo. Assumiamo di avere gia’ la variabile n. Scrivo:

fattoriale = 1
for k in range(1, n + 1):
    fattoriale = fattoriale * k

Bene. Come faccio a convertire questo codice in una funzione? Semplice:

def calcola_fattoriale(n):
    # n contiene il valore di cui voglio calcolare il fattoriale

    # qui inserisco il codice sopra
    fattoriale = 1
    for k in range(1, n+1):
        fattoriale = fattoriale * k

    # a questo punto ho calcolato il fattoriale, e lo
    # posso restituire
    return fattoriale

Ora sono libero di chiamare la funzione quante volte mi pare:

print calcola_fattoriale(1)     # 1
print calcola_fattoriale(2)     # 2
print calcola_fattoriale(3)     # 6
print calcola_fattoriale(4)     # 24
print calcola_fattoriale(5)     # 120

o anche in modi piu’ complessi:

lista = [calcola_fattoriale(n) for n in range(10)]

Avvertimento

  • Il nome della funzione ed il nome degli argomenti li scegliamo noi!

Quiz. Che differenza c’e’ tra questo frammento di codice:

def calcola(somma_o_prodotto, a, b):
    if somma_o_prodotto == "somma":
        return a + b
    elif:
        return a * b
    else:
        return 0

print calcola("somma", 10, 10)
print calcola("prodotto", 2, 2)

e questo?:

def f(operation, x, y):
    if operation == "sum":
        return x + y
    elif operation == "product":
        return x * y
    else:
        return 0

print f("sum", 10, 10)
print f("product", 2, 2)

Avvertimento

  • Il codice della funzione non vede le variabili esterne alla funzione: vede solo gli argomenti!
  • Il codice della funzione puo’ restituire un risultato solo attraverso return!

Quiz. Consideriamo questo codice:

def una_funzione(a, b):
    somma = a + b
    return somma

a = 1
b = 2
somma = 3

una_funzione(100, 100)

print somma

Cosa viene stampato a schermo?


Esempio. Definisco due funzioni:

def leggi_fasta(percorso):
    righe = open(percorso).readlines()

    dizionario = {}
    for riga in righe:
        if riga[0] == ">":
            intestazione = riga
        else:
            sequenza = riga
            dizionario[intestazione] = sequenza

    return dizionario

def calcola_istogramma(sequenza):
    istogramma = {}
    for carattere in sequenza:
        if not istogramma.has_key(carattere):
            istogramma[carattere] = 1
        else:
            istogramma[carattere] += 1
    return istogramma

Date le due funzioni, posso implementare un programma complesso che (1) legge un file fasta in un dizionario, (2) per ciascuna sequenza nel file fasta calcola l’istogramma dei nucleotidi, e (3) stampa ciascun istogramma a schermo:

dizionario_fasta = leggi_fasta(percorso)

for intestazione, sequenza in dizionario_fasta.items():
    istogramma = calcola_istogramma(sequenza)

    print istogramma

Esercizi

  1. Data la funzione:

    def funzione(arg):
        return arg
    

    di che tipo e’ il risultato delle seguenti chiamate?

    1. funzione(1)
    2. funzione({"1A3A": 123, "2B1F": 66})
    3. funzione([2*x for x in range(10)])
    4. funzione(2**-2)
  2. Data la funzione:

    def addizione(a, b):
        return a + b
    

    di che tipo e’ il risultato delle seguenti chiamate?

    1. addizione(2, 2)
    2. addizione(range(10), range(10, 20))
    3. addizione("sono una", "stringa")
  3. Creare una funzione stampa_pari_dispari() che prende un intero e stampa a schermo "pari" se il numero e’ pari e "dispari" altrimenti.

    Cosa succede se scrivo:

    risultato = stampa_pari_dispari(99)
    print risultato
    
  4. Creare una funzione calcola_pari_dispari() che prende un intero e restituisce la stringa "pari" se il numero e’ pari e la stringa "dispari" altrimenti.

    Cosa succede se scrivo:

    calcola_pari_dispari(99)
    
  5. Creare una funzione controlla_alfanumerico() che prende una stringa e restituisce True se la stringa e’ alfanumerica (contiene solo caratteri alfabetici o numerici) e False altrimenti.

    Per controllare se un carattere e’ alfanumerico, usare in e la stringa:: "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".

    Hint. Anche i caratteri minuscoli possono essere alfanumerici!

  6. Creare una funzione domanda() che non prende nessun argomento, chiede all’utente un percorso ad un file e stampa a schermo i contenuti del file. (Testarla ad esempio con il file data/aatable.)

  7. Creare una funzione wc() che prende una stringa e restituisce una tripla (tuple) di tre valori:

    1. Il primo elemento della coppia deve essere la lunghezza della stringa.
    2. Il secondo elemento deve essere il numero di a capo nella stringa.
    3. Il terzo elemento deve essere il numero di parole (separate da spazi o a capo) nella stringa.
  8. Creare una funzione stampa_dizionario() che prende un dizionario e stampa a schermo le coppie chiave-valore del dizionario formattate come da esempio sotto.

    Esempio: quando applico stampa_dizionario() a:

    dizionario = {
        "arginina": 0.7,
        "lisina": 0.1,
        "cisteina": 0.1,
        "istidina": 0.1,
    }
    

    che e’ un istogramma di frequenze, il risultato deve essere:

    >>> stampa_dizionario(dizionario)
    istidina -> 10.0%
    cisteina -> 10.0%
    arginina -> 70.0%
    lisina -> 10.0%
    

    Qui l’ordine in cui vengono stampate le righe non importa.

  9. Come sopra, ma le chiavi devono essere ordinate alfanumericamente:

    >>> stampa_dizionario_ordinato(dizionario)
    arginina -> 70%
    cisteina -> 10%
    istidina -> 10%
    lisina -> 10%
    

    Hint: conviene estrarre le chiavi del dizionario, ordinarle a parte, scorrere le chiavi ordinate e di volta in volta stampare la riga corrispondente.

  10. Creare una funzione crea_lista_di_fattoriali() che prende un intero n, e restituisce una lista di n elementi.

    L’i-esimo elemento deve essere il fattoriale di i.

    Ad esempio:

    >>> lista = crea_lista_di_fattoriali(5)
    >>> print len(lista)
    5                                       # 5 elementi, come richiesto
    >>> print lista[0]
    1                                       # e' il fattoriale di 0
    >>> print lista[1]
    1                                       # e' il fattoriale di 1
    >>> print lista[2]
    2                                       # e' il fattoriale di 2
    >>> print lista[3]
    6                                       # e' il fattoriale di 3
    >>> print lista[4]
    24                                      # e' il fattoriale di 4
    

    Hint: conviene usare la funzione fattoriale() definita in uno degli esempi precedenti per calcolare i valori della lista.

  11. Creare una funzione conta_carattere() che prende due stringhe, la prima che rappresenta testo e la seconda che rappresenta un carattere.

    La funzione deve restituire il numero di ripetizioni del carattere nel testo.

    Ad esempio:

    >>> print conta_carattere("abbaa", "a")
    3                                           # "a" compare 3 volte
    >>> print conta_carattere("abbaa", "b")
    2                                           # "b" compare 2 volte
    >>> print conta_carattere("abbaa", "?")
    0                                           # "?" non compare mai
    
  12. Creare una funzione conta_caratteri() che prende due stringhe, la prima che rappresenta testo e la seconda che rappresenta un tot di caratteri.

    La funzione deve restituire un dizionario, in cui le chiavi sono i caratteri da cercare, e il valore associato il loro numero di ripetizioni.

    Ad esempio:

    >>> print conta_caratteri("abbaa", "ab?")
    {"a": 3, "b": 2, "?": 0}
    
  13. Creare una funzione distanza() che prende due coppie (x1,y1) e (x2,y2) di punto bidimensionali e ne restituisce la distanza Euclidea.

    Hint. La distanza Euclidea e’ \sqrt{(x1-x2)^2 + (y1-y2)^2}

  14. Creare una funzione sottostringa() che date due stringhe ritorna True se la seconda e’ sottostringa della prima.

  15. Creare una funzione sottostringhe_non_vuote() che data una stringa, restituisca la lista delle sue sottostringhe non vuote.

  16. Creare una funzione conta_sottostringhe() che, date due stringhe pagliaio ed ago, ritorni il numero di ripetizioni di ago in pagliaio.

  17. Creare una funzione sottostringa_piu_lunga() che date due stringhe restituisca la loro sottostringa comune piu’ lunga.

    Hint. Si puo’ risolvere usando l’esercizio precedente!