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)

Warning

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.


Warning

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:

python3 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:

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

Warning

  • 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 somma_o_prodotto == "prodotto":
        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))

Warning

  • 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 carattere in  istogramma:
            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([1,2,3], [True, False])
    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!

Funzioni (Soluzioni)

  1. Soluzione: lo stesso tipo del valore che gli passo! La funzione restituisce il valore dell’argomento senza toccarlo.

    1. un intero.
    2. un dizionario.
    3. una lista.
    4. un razionale.
  2. Soluzione: la somma o concatenazione dei due argomenti. Quindi:

    1. un intero.
    2. una lista.
    3. una stringa.
  3. Soluzione:

    def stampa_pari_dispari(numero):
        if numero % 2 == 0:
            print("pari")
        else:
            print("dispari")
    
    stampa_pari_dispari(98)
    stampa_pari_dispari(99)
    

    Occhio che stampa_pari_dispari() stampa gia’ da se’ a schermo, non e’ necessario fare:

    print(stampa_pari_dispari(99))
    

    altrimenti Python stampera’:

    pari
    None
    

    Il None viene dal print aggiuntivo: visto che stampa_pari_dispari() non ha return, il suo risultato e’ sempre None. Il print aggiuntivo stampa proprio questo None.

  4. Soluzione:

    def calcola_pari_dispari(numero):
        if numero % 2 == 0:
            return "pari"
        else:
            return "dispari"
    
    print(calcola_pari_dispari(98))
    print(calcola_pari_dispari(99))
    

    In questo caso invece, visto che non c’e’ nessun print in calcola_pari_dispari(), e’ necessario aggiungere a mano un print che ne stampi il risultato!

  5. Soluzione:

    def controlla_alfanumerico(stringa):
        caratteri_alfanumerici = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    
        alfanumerica = True
        for carattere in stringa:
            if not carattere in caratteri_alfanumerici:
                alfanumerica = False
    
        return alfanumerica
    
    # testo la funzione
    print(controlla_alfanumerico("ABC123"))
    print(controlla_alfanumerico("A!%$*@"))
    

    Posso anche usare break per interrompere il ciclo for appena trovo un carattere alfanumerico (e’ impossibile che una stringa dove ho appena trovato un carattere non-alfanumerico ridiventi alfanumerica), cosi’:

    def controlla_alfanumerico(stringa):
        caratteri_alfanumerici = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    
        alfanumerica = True
        for carattere in stringa:
            if not carattere in caratteri_alfanumerici:
                alfanumerica = False
                break # <-- esco dal for
    
        # <-- il break mi fa arrivare qui
        return alfanumerica
    
    # testo la funzione
    print(controlla_alfanumerico("ABC123"))
    print(controlla_alfanumerico("A!%$*@"))
    

    In alternativa, visto che quando faccio break arrivo direttamente al return, posso saltare un passaggio e fare direttamente return:

    def controlla_alfanumerico(stringa):
        caratteri_alfanumerici = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    
        for carattere in stringa:
            if not carattere.upper() in caratteri_alfanumerici:
                # ho incontrato un carattere non alfanumerico
                # posso rispondere False
                return False
    
        # arrivo alla fine del for solo se non ho mai fatto `return`, il
        # che succede solo se la condizione dell'`if` e' sempre stata False
        # per tutti i caratteri: vuol dire che sono tutti caratteri alfanumerici
        # rispondo True
        return True
    
    # testo la funzione
    print(controlla_alfanumerico("ABC123"))
    print(controlla_alfanumerico("A!%$*@"))
    
  6. Soluzione:

    def domanda():
        percorso = input("scrivi un percorso: ")
        print(open(percorso).readlines())
    
    # la testo
    domanda()
    
  7. Soluzione:

    def wc(stringa):
        num_caratteri = len(stringa)
        num_a_capo = stringa.count("\n")
        num_parole = len(stringa.split()) # split rompe sia sugli spazi che sugli a-capo
        return (num_caratteri, num_a_capo, num_parole)
    
    # la testo
    print(wc("sono\nuna bella\nstringa"))
    
  8. Soluzione:

    def stampa_dizionario(un_dizionario):
        # l'ordine in cui vanno stampate le righe non importa, percio'
        # posso usare l'ordine naturale in cui mi vengono fornite da
        # `items()`
        for chiave, valore in un_dizionario.items():
            print(chiave, "->", (str(valore * 100.0) + "%"))
    
    # la testo
    
    dizionario = {
        "arginina": 0.7,
        "lisina": 0.1,
        "cisteina": 0.1,
        "istidina": 0.1,
    }
    
    stampa_dizionario(dizionario)
    
  9. Soluzione:

    def stampa_dizionario_ordinato(un_dizionario):
        # estraggo le chiavi e le ordino
        chiavi_ordinate = list(un_dizionario.keys())
        chiavi_ordinate.sort()
    
        # ora stampo le coppie chiave-valore in ordine
        for chiave in chiavi_ordinate:
            valore = un_dizionario[chiave]
    
            print(chiave, "->", (str(valore * 100.0) + "%"))
    
    # la testo
    
    dizionario = {
        "arginina": 0.7,
        "lisina": 0.1,
        "cisteina": 0.1,
        "istidina": 0.1,
    }
    
    stampa_dizionario_ordinato(dizionario)
    
  10. Soluzione:

    # presa dall'esempio
    def calcola_fattoriale(n):
        fattoriale = 1
        for k in range(1, n+1):
            fattoriale = fattoriale * k
        return fattoriale
    
    def crea_lista_di_fattoriali(n):
        lista_di_fattoriali = []
        for i in range(n):
            lista_di_fattoriali.append(calcola_fattoriale(i))
        return lista_di_fattoriali
    
    # la testo
    print(crea_lista_di_fattoriali(2))
    print(crea_lista_di_fattoriali(4))
    print(crea_lista_di_fattoriali(6))
    

    qui ho riutilizzato la funzione calcola_fattoriale() definita in uno degli esempi.

  11. Soluzione:

    def conta_carattere(testo, carattere_voluto):
        conta = 0
        for carattere in testo:
            if carattere == carattere_voluto:
                conta += 1
        return conta
    
    # la testo
    print(conta_carattere("abbaa", "a"))
    print(conta_carattere("abbaa", "b"))
    print(conta_carattere("abbaa", "?"))
    

    oppure, piu’ semplicemente, posso sfruttare count():

    def conta_carattere(testo, carattere_voluto):
        return testo.count(carattere_voluto)
    
    # la testo
    print(conta_carattere("abbaa", "a"))
    print(conta_carattere("abbaa", "b"))
    print(conta_carattere("abbaa", "?"))
    
  12. Soluzione:

    def conta_caratteri(testo, caratteri_voluti):
        conta = {}
        for carattere_voluto in caratteri_voluti:
            conta[carattere_voluto] = conta_carattere(testo, carattere_voluto)
        return conta
    
    # la testo
    print(conta_caratteri("abbaa", "ab?"))
    

    dove ho riutilizzato la funzione dell’esercizio precedente.

  13. Soluzione:

    def distanza(coppia1, coppia2):
        x1, y1 = coppia1
        x2, y2 = coppia2
    
        dx = x1 - x2
        dy = y1 - y2
    
        return (float(dx)**2 + float(dy)**2)**0.5
    
    # la testo
    print(distanza((0, 0), (1, 1)))
    print(distanza((2, 3), (3, 2)))
    
  14. Soluzione:

    def sottostringa(prima, seconda):
        return seconda in prima
    
    # la testo
    print(sottostringa("ACGT", "T"))
    print(sottostringa("ACGT", "x"))
    
  15. Soluzione:

    def sottostringhe_non_vuote(stringa):
        sottostringhe = []
    
        # tutte le possibili posizioni in cui far iniziare la sottostringa
        for inizio in range(len(stringa)):
    
            # tutte le poss. posizioni in cui far finire la sottostringa
            for fine in range(inizio + 1, len(stringa) + 1):
    
                # estraggo la sottostringa ed aggiorno la lista
                sottostringhe.append(stringa[inizio:fine])
        return sottostringhe
    
    # la testo
    print(sottostringhe_non_vuote("ACTG"))
    
  16. Soluzione:

    def conta_sottostringhe(pagliaio, ago):
        ripetizioni = 0
        for inizio in range(len(pagliaio)):
            for fine in range(inizio+1, len(pagliaio)+1):
    
                # stampo quanto vale la sottostringa, per sicurezza
                print(inizio, fine, ":", pagliaio[inizio:fine], "==", ago, "?")
    
                # controllo se la sottostringa e' uguale ad `ago`
                if pagliaio[inizio:fine] == ago:
                    print("ho trovato una ripetizione!")
                    ripetizioni += 1
    
        return ripetizioni
    
    # la testo
    print(conta_sottostringhe("ACTGXACTG", "ACTG"))
    
  17. Soluzione:

    def sottostringa_piu_lunga(stringa1, stringa2):
    
        # riutilizzo la soluzione sopra
        sottostringhe1 = sottostringhe_non_vuote(stringa1)
        sottostringhe2 = sottostringhe_non_vuote(stringa2)
    
        # controllo tra tutte le coppie di sottostringhe
        # quale e' quella piu' lunga che appare sia in
        # stringa1 che in stringa2
        piu_lunga = ""
        for sottostringa1 in sottostringhe1:
            for sottostringa2 in sottostringhe2:
    
                # se sono uguali e piu' lunghe della sottostringa
                # comune piu' lunga trovata fin'ora...
                if sottostringa1 == sottostringa2 and \
                   len(sottostringa1) > len(piu_lunga):
    
                    # aggiorno
                    piu_lunga = sottostringa1
    
        return piu_lunga
    
    # la testo
    print(sottostringa_piu_lunga("ACTG", "GCTA"))
    print(sottostringa_piu_lunga("", "ACTG"))
    print(sottostringa_piu_lunga("ACTG", ""))