Funzioni 1 - introduzione

Scarica zip esercizi

Naviga file online

Una funzione è del codice che prende in input dei parametri e li usa per produrre o riportare qualche risultato.

ATTENZIONE: questa guida è IN PROGRESS

Per maggiori informazioni, vedere Pensare in Python, di Allen B. Downey

Cosa fare

  • scompatta lo zip in una cartella, dovresti ottenere qualcosa del genere:

functions
    fun1-intro.ipynb
    fun1-intro-sol.ipynb
    fun2-errors-and-testing.ipynb
    fun2-errors-and-testing-sol.ipynb
    fun3-strings.ipynb
    fun3-strings-sol.ipynb
    fun4-lists.ipynb
    fun4-lists-sol.ipynb
    fun5-tuples.ipynb
    fun5-tuples-sol.ipynb
    fun6-sets.ipynb
    fun6-sets-sol.ipynb
    fun7-dictionaries.ipynb
    fun7-dictionaries-sol.ipynb
    fun8-chal.ipynb
    jupman.py

ATTENZIONE: Per essere visualizzato correttamente, il file del notebook DEVE essere nella cartella szippata.

  • apri il Jupyter Notebook da quella cartella. Due cose dovrebbero aprirsi, prima una console e poi un browser. Il browser dovrebbe mostrare una lista di file: naviga la lista e apri il notebook fun1-intro.ipynb

  • Prosegui leggendo il file degli esercizi, seguendo le istruzioni

Scorciatoie da tastiera:

  • Per eseguire il codice Python dentro una cella di Jupyter, premi Control+Invio

  • Per eseguire il codice Python dentro una cella di Jupyter E selezionare la cella seguente, premi Shift+Invio

  • Per eseguire il codice Python dentro una cella di Jupyter E creare una nuova cella subito dopo, premi Alt+Invio

  • Se per caso il Notebook sembra inchiodato, prova a selezionare Kernel -> Restart

Perchè le funzioni?

Ci sono diversi motivi per cui potremmo voler crearci le nostre funzioni, per esempio:

  • Ridurre la duplicazione del codice: mettere nelle funzioni parti di codice che sono necessarie diverse volte nell’intero programma, così non hai bisogno di ripetere lo stesso codice più volte;

  • Decomporre un task complesso: rendere il codice più semplice da scrivere e comprendere separando un programma intero in funzioni più semplici.

DOMANDE: Per ciascun frammento di codice che segue, prova a indovinare che risultato produce (o se da errore)

  1. def f():
    print('car')
    f()
    
  2. def f():
        print('car')
    f()
    
  3. def f():
    return 3
    f()
    
  4. def f():
        return 3
    f()
    
  5. def f():
        return 3
    f()f()
    
  6. def f():
        return 3
    f()*f()
    
  7. def f():
        pass
    f()
    
  8. def f():
        pass
        print("ciao")
    f()
    
  9. def f(x):
        return x
    f()
    
  10. def f(x):
        return x
    f(5)
    
  11. def f():
        print('fire')
    x = f()
    print(x)
    
  12. def f():
        return(print('fire'))
    print(f())
    
  13. def f(x):
        return 'x'
    print(f(5))
    
  14. def f(x):
        return x
    print(f(5))
    
  15. def etc():
        print('etc...')
        return etc()
    etc()
    
  16. def gu():
        print('GU')
        ru()
    
    def ru():
        print('RU')
        gu()
    
    gu()
    

Saper distinguere le funzioni

Puoi trovare all’incirca 5 categorie di funzioni nel mondo là fuori:

  1. PRODUCE SIDE EFFECTS:

    • STAMPA / RICHIEDE INPUT MANUALE / SCRIVE modificando l’ambiente in qualche modo, per esempio stampando caratteri sullo schermo, chiedendo in modo interattivo dati all’utente o scrivendo in un file.

  2. RITORNA un valore

  3. MODIFICA l’input

  4. MODIFICA l’input e lo RITORNA (permette la concatenazione di chiamate, detto chaining)

  5. MODIFICA l’input e RITORNA qualcosa derivato dall’input

Proviamo ora a capire le differenze con diversi esempi.

SIDE EFFECTS

Fa solo una STAMPA, RICHIEDE INPUT INTERATTIVO oppure SCRIVE SU FILE

  • NON modifica l’input!

  • NON ritorna niente!

Esempio:

[2]:
def stampola(lista):
    """STAMPA i primi due elementi della lista data
    """
    print('I primi due elementi sono', lista[0], 'e', lista[1])

la = [8,5,6,2]

stampola(la)
jupman.pytut()
I primi due elementi sono 8 e 5
[2]:
Python Tutor visualization

RITORNA

RITORNA qualche valore, come una NUOVA regione di memoria o un puntatore a una regione di memoria esistente, in accordo al testo della funzione

  • NON modifica l’input

  • NON stampa niente!

Esempio:

[3]:
def ritornola(lista):
    """RITORNA una NUOVA lista che ha tutti i numeri di lista raddoppiati
    """
    ret = []
    for el in lista:
        ret.append(el*2)
    return ret

la = [5,2,6,3]
risultato = ritornola(la)
print("       la:", la)
print("risultato:", risultato)
jupman.pytut()
       la: [5, 2, 6, 3]
risultato: [10, 4, 12, 6]
[3]:
Python Tutor visualization

MODIFICA

MODIFICA l’input - con la scritta modifica tipicamente indichiamo cambiare i dati dentro regioni di memoria esistenti, limitando il più possibile la creazione di regioni nuove.

  • NON ritorna niente!

  • NON stampa niente!

  • NON crea nuove regioni di memoria (o limita la creazione al minimo necessario)

Esempio:

[4]:
def modifanta(lista):
    """MODIFICA lista in modo che sia ordinata in-place
    """
    lista.sort()

la = [7,4,9,8]

modifanta(la)

print(la)
jupman.pytut()
[4, 7, 8, 9]
[4]:
Python Tutor visualization

MODIFICA E RITORNA

MODIFICA l’input e RITORNA un puntatore all’input stesso

  • NON STAMPA niente!

  • NON crea nuove regioni di memoria (o limita la creazione al minimo necessario)

Nota: permette così il concatenamento di chiamate (detto chaining)

[5]:
def modirit(lista):
    """MODIFICA lista raddoppiando tutti i suoi elementi,
       e infine RITORNA la lista di input
    """
    for i in range(len(lista)):
        lista[i] = lista[i] * 2
    return lista

la = [8,7,5]
risultato = modirit(la)
print("risultato: ", risultato)    # [16,14,10]  ha RITORNATO l'input modificato
print("       la: ", la)           # [16,14,10]  l'input la è stato MODIFICATO !!

print()
lb = [7,5,6]
modirit(lb).reverse()              # NOTA CHE POSSIAMO CONCATENARE
print("       lb: ", lb)           # [12,10,14]  l'input lb è stato MODIFICATO !!
#modirit(lb).reverse().append(16)  # ... ma questo non funzionerebbe. Perchè?

jupman.pytut()
risultato:  [16, 14, 10]
       la:  [16, 14, 10]

       lb:  [12, 10, 14]
[5]:
Python Tutor visualization

MODIFICA E RITORNA UNA PARTE

MODIFICA l’input e RITORNA una parte di esso

  • NON STAMPA niente!

[6]:
def modirip(lista):
    """MODIFICA lista affinchè diventi ordinata e l'elemento più grande sia rimosso,
       infine RITORNA l'elemento rimosso
    """
    lista.sort()
    ret = lista[-1]
    lista.pop()
    return ret

la = ['b','c','a']
risultato = modirip(la)
print("risultato:", risultato)    # 'c'        : ha RITORNATO un pezzo di input
print("       la:", la)           # ['a','b']  : la è stata MODIFICATA!!
jupman.pytut()
risultato: c
       la: ['a', 'b']
[6]:
Python Tutor visualization

Ricorda i comandamenti

III COMANDAMENTO

Non riassegnerai mai parametri di funzione

Non farai mai nessuna di queste assegnazioni, pena la perdita del parametro passato quando viene chiamata la funzione:

[7]:
def peccato(intero):
    intero = 666            # peccato, hai perso il 5 passato dall'esterno !
    print(intero)           # stampa 666

x = 5
peccato(x)
666

Lo stesso discorso si applica per tutti gli altri tipi:

[8]:
def male(stringa):
    stringa = "666"
[9]:
def disgrazia(lista):
    lista = [666]
[10]:
def delirio(dizionario):
    dizionario = {"maligno":666}

Per il solo caso di parametri compositi come liste o dizionari, puoi scrivere come sotto SE E SOLO SE le specifiche della funzione ti richiedono di MODIFICARE gli elementi interni del parametro (come per esempio ordinare una lista o cambiare il campo di un dizionario)

[11]:
# MODIFICA lista in qualche modo
def consentito(lista):
    lista[2] = 9         # OK, lo richiede il testo della funzione

fuori = [8,5,7]
consentito(fuori)
print(fuori)
[8, 5, 9]
[12]:
# MODIFICA dizionario in qualche modo
def daccordo(dizionario):
    dizionario["mio campo"] = 5       # OK, lo richiede il testo
[13]:
# MODIFICA istanza in qualche modo
def va_bene(istanza_di_classe):
    istanza_di_classe.mio_campo = 7   # OK, lo richiede il testo

Se invece il testo di una funzione ti chiede di RITORNARE un NUOVO oggetto, non cadrai nella tentazione di modificare l’input:

[14]:
# RITORNA una NUOVA lista ordinata
def dolore(lista):
    lista.sort()           # MALE, stai modificando la lista di input invece di crearne una nuova!
    return lista
[15]:
# RITORNA una NUOVA lista
def crisi(lista):
    lista[0] = 5           # MALE, come sopra
    return lista
[16]:
# RITORNA un NUOVO dizionario
def tormento(dizionario):
    dizionario['a'] = 6    # MALE, stai modificando il dizionario di input
                           #       invece di crearne uno nuovo!
    return dizionario
[17]:
# RITORNA una NUOVA istanza di classe
def disperazione(istanza):
    istanza.mio_campo = 6  # MALE, stai modificando l'oggetto di input
                           #       invece di crearne uno nuovo!
    return istanza

IV COMANDAMENTO

Non riassegnerai mai valori a chiamate a funzioni o metodi

Chiamate a funzione come mia_funzione() ritornano risultati di calcoli e li mettono in una scatola che è creata solo per lo scopo della chiamata e Python non ci consentirà di riusarla come una variabile.

Quando vedi nome() alla parte sinistra, non può essere seguito da un segno di uguaglianza = (ma può essere seguito da due segni di uguaglianza == se stai eseguendo una comparazione).

SBAGLIATO:

mia_funzione() = 666
mia_funzione() = 'evil'
mia_funzione() = [666]

CORRETTO:

x = 5
y = my_fun()
z = []
z[0] = 7
d = dict()
d["a"] = 6

V COMANDAMENTO

Non ridifinerai mai funzioni di sistema

Python ha diverse funzioni di sistema predefinite. Per esempio list è un tipo Python: come tale, puoi usarlo per esempio come funzione per convertire un qualche tipo a lista:

[18]:
list("ciao")
[18]:
['c', 'i', 'a', 'o']

Quando consenti alle forze del male di prendere il sopravvento, potresti essere tentato di usare tipi e funzioni di sistema (per es. list) come una variabile per i tuoi miserabili propositi personali:

list = ['la', 'mia', 'lista', 'raccapricciante']

Python ti permette di farlo, ma noi no, poichè le conseguenze sono disastrose.

Per esempio, se adesso usi list per il proposito per cui è stata creata, cioè conversione a lista, non funzionerà più:

list("ciao")
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-c63add832213> in <module>()
----> 1 list("ciao")

TypeError: 'list' object is not callable

In particolare, raccomandiamo di non ridefinire queste preziose funzioni:

  • bool, int,float,tuple,str,list,set,dict

  • max, min, sum

  • next, iter

  • id, dir, vars,help

Valori immutabili

I tipi base come gli interi, float e booleani sono immutabili, così come le sequenze stringhe e tuple: quando ti viene chiesto di RITORNARE uno di questi tipi, diciamo una stringa, la sola cosa che puoi fare è ottenere NUOVE stringhe basate sui parametri che ricevi. Vediamo un esempio.

Supponi ti venga richiesto di implementare questa funzione:

Scrivi una funzione mia_maiuscche RITORNI la stringa passata in maiuscolo.

Potremmo implementarla così:

[19]:
# Esegui questa cella per far funzionare Python Tutor
import jupman;
[20]:
stringa_esterna = "marinaio"

def mia_maiusc(s):
    ret = s.upper()   # i metodi delle stringhe creano una NUOVA stringa
    return ret

risultato = mia_maiusc(stringa_esterna)

print('      risultato:', risultato)
print('stringa_esterna:', stringa_esterna)

jupman.pytut()
      risultato: MARINAIO
stringa_esterna: marinaio
[20]:
Python Tutor visualization

Nota alcune cose:

  • la stringa_esterna non è cambiata

  • non abbiamo scritto s = dentro il corpo della funzione, perchè il IV COMMANDMENTO prescrive di non riassegnare i parametri

  • non ci siamo riferiti a stringa_esterna dentro il corpo della funzione: fare una cosa del genere avrebbe invalidato il proposito delle funzioni, che è proprio isolarle per quanto possibile dal mondo esterno

Cambiare il mondo: fallimento / 1

E se avessimo voluto veramente cambiare l’assegnazione di stringa_esterna ?

Potresti essere tentato di scrivere qualcosa come un assegnamento s = proprio dentro la funzione, ma il codice seguente non funzionerà

DOMANDA: Perchè? Prova a rispondere prima di verificare l’esecuzione in Python Tutor.

Mostra risposta
[21]:
stringa_esterna = "marinaio"

def mia_maiusc(s):
    s = s.upper()
    return s

risultato = mia_maiusc(stringa_esterna)

print('      risultato:', risultato)
print('stringa_esterna:', stringa_esterna)

jupman.pytut()
      risultato: MARINAIO
stringa_esterna: marinaio
[21]:
Python Tutor visualization

Cambiare il mondo: fallimento / 2

Vediamo un’altra tentazione. Potresti provare ad assegnare stringa_esterna = dentro la funzione, ma il codice seguente non funzionerà

DOMANDA: Perchè? Prova a rispondere prima di controllare l’esecuzione in Python Tutor.

Mostra risposta
[22]:
stringa_esterna = "marinaio"

def mia_maiusc(s):
    stringa_esterna = s.upper()
    return stringa_esterna

risultato = mia_maiusc(stringa_esterna)

print('      risultato:', risultato)
print('stringa_esterna:', stringa_esterna)

jupman.pytut()
      risultato: MARINAIO
stringa_esterna: marinaio
[22]:
Python Tutor visualization

Cambiare il mondo: Successo!

Il modo corretto di affrontare il problema è creare una NUOVA stringa dentro la funzione, ritornarla, e quindi fuori dalla funzione effettuare l’assegnazione stringa_esterna = risultato.

[23]:
stringa_esterna = "marinaio"

def mia_maiusc(s):
    ret = s.upper()
    return ret

risultato = mia_maiusc(stringa_esterna)
stringa_esterna = risultato             # riassegna *fuori*

print('      risultato:', risultato)
print('stringa_esterna:', stringa_esterna)

jupman.pytut()
      risultato: MARINAIO
stringa_esterna: MARINAIO
[23]:
Python Tutor visualization

Parola chiave global

Se proprio volessimo modificare l’associazione stringa_esterna dentro la funzione, potremmo farlo con la parola chiave global, ma tipicamente è meglio evitare di usarla per tenere il codice pulito.

Valori mutabili

Sequenze come le liste, insiemi e dizionari sono oggetti mutabili. Quando chiami una funzione e passi uno di questi oggetti, Python in realtà fornisce alla funzione solo un riferimento all’oggetto: un puntatore molto piccolo che è solo una freccia che punta alla regione di memoria dove risiede l’oggetto reale. Dato che la funzione riceve solo un piccolo puntatore, chiamare una funzione è un’operazione veloce. D’altro canto, dobbiamo essere consci del fatto che dato che nessuna copia viene effettuata, dentro la funzione sarà come operare sulla regione di memoria originale che vive al di fuori della chiamata di funzione.

Tutto ciò potrebbe sembrare uno sciogli-lingua, perciò vediamo un esempio pratico in Python Tutor. Supponiamo di dover implementare questa funzione:

Scrivi una funzione che prende una lista e la MODIFICA raddoppiando tutti i suoi numeri

Nota che nel testo abbiamo usato la parola MODIFICA, col significato di voler veramente modificare la regione di memoria originale dell’oggetto esterno che ci viene consegnato.

Per quanto possa sembrare semplice, ci sono parecchi modi in cui le cose potrebbero andare storte. Vediamone qualcuno.

Raddoppiare: fallimento / 1

Potreste essere tentati di risolvere il problema come nel codice seguente, ma non funzionerà.

DOMANDA: Perchè? Prova a rispondere prima di controllare l’esecuzione in Python Tutor.

Mostra risposta
[24]:
numeri_esterni = [10,20,30]

def raddoppia(lista):
    for elemento in lista:
        elemento = elemento * 2

raddoppia(numeri_esterni)

jupman.pytut()
[24]:
Python Tutor visualization

Raddoppiare: fallimento / 2

Potresti avere un’altra tentazione di risolvere il problema come nel codice seguente, ma di nuovo non funzionerà.

DOMANDA: Perchè? Prova a rispondere prima di controllare l’esecuzione in Python Tutor.

Mostra risposta
[25]:
numeri_esterni = [10,20,30]

def raddoppia(lista):
    tmp = []
    for elemento in lista:
        tmp.append(elemento * 2)

    lista = tmp

raddoppia(numeri_esterni)

jupman.pytut()
[25]:
Python Tutor visualization

Raddoppiare: fallimento / 3

Un’altra tentazione potrebbe essere la seguente, ma ancora una volta il codice non funzionerà.

DOMANDA: Perchè? Prova a rispondere prima di controllare l’esecuzione in Python Tutor.

Mostra risposta
[26]:
numeri_esterni = [10,20,30]

def raddoppia(lista):
    tmp = []
    for elemento in lista:
        tmp.append(elemento * 2)

    numeri_esterni = tmp

raddoppia(numeri_esterni)

jupman.pytut()
[26]:
Python Tutor visualization

Raddoppiare: fallimento / 4

Infine, vediamo l’ultima tentazione che come immaginerai non funzionerà.

DOMANDA: Perchè? Prova a rispondere prima di controllare l’esecuzione in Python Tutor.

Mostra risposta
[27]:
numeri_esterni = [10,20,30]

def raddoppia(lst):
    tmp = []
    for element in lst:
        tmp.append(element * 2)

    return tmp

numeri_esterni = raddoppia(numeri_esterni)

jupman.pytut()
[27]:
Python Tutor visualization

Probabilmente sei un po’ confuso riguardo il tentativo precendente, perchè all’occhio non esperto potrebbe sembrare che faccia quanto desiderato. Proviamo a riscriverlo con una variabile extra salvata che punterà esattamente alla stessa regione di memoria originale di numeri_esterni. Vedrai che alla fine salvato punterà a [10,20,30], mostrando che non abbiamo veramente MODIFICATO la regione di memoria originale.

[28]:
numeri_esterni = [10,20,30]
salvata = numeri_esterni   # preserviamo il puntatore

def raddoppia(lista):
    tmp = []
    for elemento in lista:
        tmp.append(elemento * 2)
    return tmp

numeri_esterni = raddoppia(numeri_esterni)
print('numeri_esterni:', numeri_esterni)  # [20,40,60]
print('       salvata:', salvata)         # [10,20,30]

jupman.pytut()
numeri_esterni: [20, 40, 60]
       salvata: [10, 20, 30]
[28]:
Python Tutor visualization

Raddoppiare: successo!

Vediamo infine il modo giusto di affrontare il problema: dobbiamo considerare che vogliamo riferirci alle celle originali, e per farlo propriamente dobbiamo accederle per indice. Avremo quindi bisogno di un for in range:

[29]:

numeri_esterni = [1,2,3,4,5] def raddoppia(lista): for i in range(len(lista)): lista[i] = lista[i] * 2 raddoppia(numeri_esterni) jupman.pytut()
[29]:
Python Tutor visualization

Nota che:

  • quando il frame di chiamata della funzione è creato, vediamo una freccia ai dati originali

  • la lista_esterna è davvero mutata, senza averla mai riassegnata (nemmeno fuori)

  • non abbiamo riassegnato lista = dentro il corpo della funzione, in accordo al IV COMANDMENTO che prescrive di non riassegnare i parametri

  • non abbiamo usato return, perchè il testo della funzione non ha menzionato di ritornare alcunchè

  • non ci siamo riferiti a lista_esterna dentro il corpo della funzione: farlo avrebbe sconfitto il proposito delle funzioni, che è isolarle il più possibile dal mondo esterno.

In generale, nel caso di dati mutabili l’isolamento dei dati non è mai stretto, perchè otteniamo puntatori a dati che vivono fuori dal frame della funzione. Quando manipoliamo puntatori, la responsabilità di trattarli con particolare attenzione cade su di noi.

Modificare parametri - domande

Per ciascuna dei seguenti frammenti di codice, prova a indovinare il risultato che produce (o se da errore)

  1. def zam(bal):
        bal = 4
    x = 8
    zam(x)
    print(x)
    
  2. def zom(y):
        y = 4
    y = 8
    zom(y)
    print(y)
    
  3. def per(la):
        la.append('è')
    per(la)
    print(la)
    
  4. def zeb(lst):
        lst.append('d')
    la = ['a','b','c']
    zeb(la)
    print(la)
    
  5. def attenzione(la):
        la = ['?','?']
    lb = ['c','a','s','p','i','t','a']
    attenzione(lb)
    print(lb)
    
  6. def umpa(string):
        string = "lompa"
    word = "gnappa"
    umpa(word)
    print(word)
    
  7. def sport(diz):
        diz['scarpe'] = 2
    armadio = {'racchette':4,
               'palline': 7}
    sport(armadio)
    print(armadio)
    
  8. def numma(lst):
        lst + [4,5]
    la = [1,2,3]
    print(numma(la))
    print(la)
    
  9. def truc(lista):
        return lista + [4,5]
    lb = [1,2,3]
    print(truc(lb))
    print(lb)
    

Esercizi - Si cambia musica

E’ arrivato il momento di capire meglio cosa stiamo facendo quando traffichiamo con variabili e chiamate di funzioni.

Abbiamo un album di canzoni impolverato che ci ha passato uno zio nostalgico, che chissà perchè da decenni si rifiuta di accendere la radio:

[30]:
album = [
    "Caterina Caselli - Cento giorni",
    "Delirium - Jesahel",
    "Jan Hammer - Crockett's Theme",
    "Sonata Arctica - White Pearl, Black Oceans",
    "Lucio Dalla - 4 marzo 1943.mp3",
    "The Wellermen - Wellerman",
    "Manu Chao - Por el Suelo",
    "Intillimani - El Pueblo Unido"
]

Le canzoni sono riportate con il gruppo, un trattino - e infine il nome. Forti delle nostre nuove conoscenze, decidiamo di avvalerci delle moderne tecniche di sviluppo software per analizzare questi misteriosi reperti audio del passato.

Di seguito troverai diversi esercizi che ti chiederanno di sviluppare delle funzioni: spesso realizzare qualcosa che sembra funzionare è relativamente facile, la vera sfida è seguire fedelmente quanto richiesto dal testo della funzione: poni particolare attenzione alle parole in maiuscolo, come STAMPA, MODIFICA, RITORNA, e agli output desiderati, cercando di capire a quale categoria appartengono le varie funzioni.

Gli esercizi vanno tutti risolti seguendo questo schema:

album = ...

def funz(canzoni):
    # fai qualcosa con canzoni, NON con album
    # ....

funz(album)  # chiamate per testare, esterne al corpo della funzione

NON SCRIVERE NOMI DI VARIABILI ESTERNE DENTRO LA FUNZIONE

Per esempio, NON scrivere album, in particolare NON riassegnarlo (niente album =)

Una funzione tipicamente va vista come un mondo isolato, che dovrebbe interagire con l’esterno SOLO attraverso i parametri passati. Scrivere esplicitamente album scavalca questo isolamento ed è garantito portare sfortuna.

USA SEMPRE UN NOME DI PARAMETRO DIVERSO DALLE VARIABILI ESTERNE

Per esempio, se i dati esterni si chiamano album, puoi chiamare il parametro canzoni

Esercizio - mostra

Scrivi una funzione che data una lista di canzoni, le STAMPA con il gruppo giustificato a destra seguito da : e il nome della canzone

  • SUGGERIMENTO: per giustificare il testo, usa il metodo delle stringhe .rjust(16)

>>> ris = mostra(album)  # stampa solo, implicitamente ritorna None
Caterina Caselli: Cento giorni
        Delirium: Jesahel
      Jan Hammer: Crockett's Theme
  Sonata Arctica: White Pearl, Black Oceans
     Lucio Dalla: 4 marzo 1943.mp3
   The Wellermen: Wellerman
       Manu Chao: Por el Suelo
     Intillimani: El Pueblo Unido
>>> print(ris)
None
Mostra soluzione
[31]:

album = [ "Caterina Caselli - Cento giorni", "Delirium - Jesahel", "Jan Hammer - Crockett's Theme", "Sonata Arctica - White Pearl, Black Oceans", "Lucio Dalla - 4 marzo 1943.mp3", "The Wellermen - Wellerman", "Manu Chao - Por el Suelo", "Intillimani - El Pueblo Unido" ] # scrivi qui

Esercizio - autori

Scrivi una funzione che data una lista di canzoni RITORNA una NUOVA lista con solo gli autori

>>> autori(album)
['Caterina Caselli', 'Delirium', 'Jan Hammer', 'Sonata Arctica', 'Lucio Dalla', 'The Wellermen', 'Manu Chao', 'Intillimani']

>>> album
['Caterina Caselli - Cento giorni',
 'Delirium - Jesahel',
 "Jan Hammer - Crockett's Theme",
 'Sonata Arctica - White Pearl, Black Oceans',
 "Lucio Dalla - 4 marzo 1943.mp3",
 'The Wellermen - Wellerman',
 'Manu Chao - Por el Suelo',
 'Intillimani - El Pueblo Unido']
Mostra soluzione
[32]:

album = [ "Caterina Caselli - Cento giorni", "Delirium - Jesahel", "Jan Hammer - Crockett's Theme", "Sonata Arctica - White Pearl, Black Oceans", "Lucio Dalla - 4 marzo 1943.mp3", "The Wellermen - Wellerman", "Manu Chao - Por el Suelo", "Intillimani - El Pueblo Unido" ] # scrivi qui

Esercizio - registra

Scrivi una funzione che date due liste canzoniA e canzoniB, MODIFICA canzoniA sovrascrivendola con il contenuto di canzoniB. Se canzoniB ha meno elementi di canzoniA, riempi gli spazi restanti con None.

  • supponi che canzoniB abbia al massimo lo stesso numero di canzoni di canzoniA

  • NON riassegnare album (niente album = )

# non ritorna nulla !
>>> registra(album, ["Toto Cotugno - L'Italiano vero", "Mia Martini - Minuetto", "Al Bano-NEL SOLE"])

>>> album   # il parametro è stato modificato
["Toto Cotugno - L'Italiano vero",
 "Mia Martini - Minuetto",
 "Al Bano - Nel sole",
 None,
 None,
 None,
 None,
 None]
Mostra soluzione
[33]:

album = [ "Caterina Caselli - Cento giorni", "Delirium - Jesahel", "Jan Hammer - Crockett's Theme", "Sonata Arctica - White Pearl, Black Oceans", "Lucio Dalla - 4 marzo 1943.mp3", "The Wellermen - Wellerman", "Manu Chao - Por el Suelo", "Intillimani - El Pueblo Unido" ] # scrivi qui

Esercizio - grande

Scrivi una funzione che data una lista di canzoni MODIFICA la lista ponendo tutti i caratteri in maiuscolo, e poi la RITORNA

  • NON riassegnare album (niente album =)

Esempio:

>>> grande(album)   # ritorna
['CATERINA CASELLI - CENTO GIORNI',
 'DELIRIUM - JESAHEL',
 "JAN HAMMER - CROCKETT'S THEME",
 'SONATA ARCTICA - WHITE PEARL, BLACK OCEANS',
 'LUCIO DALLA - 4 MARZO 1943.MP3',
 'THE WELLERMEN - WELLERMAN',
 'MANU CHAO - POR EL SUELO',
 'INTILLIMANI - EL PUEBLO UNIDO']

>>> album          # il parametro è stato modificato
['CATERINA CASELLI - CENTO GIORNI',
 'DELIRIUM - JESAHEL',
 "JAN HAMMER - CROCKETT'S THEME",
 'SONATA ARCTICA - WHITE PEARL, BLACK OCEANS',
 'LUCIO DALLA - 4 MARZO 1943.MP3',
 'THE WELLERMEN - WELLERMAN',
 'MANU CHAO - POR EL SUELO',
 'INTILLIMANI - EL PUEBLO UNIDO']
Mostra soluzione
[34]:

album = [ "Caterina Caselli - Cento giorni", "Delirium - Jesahel", "Jan Hammer - Crockett's Theme", "Sonata Arctica - White Pearl, Black Oceans", "Lucio Dalla - 4 marzo 1943.mp3", "The Wellermen - Wellerman", "Manu Chao - Por el Suelo", "Intillimani - El Pueblo Unido" ] # scrivi qui

Esercizio - accorcia

Scrivi una funzione che data una lista di canzoni e un numero n, MODIFICA canzoni accorciandola affinchè abbia solo n canzoni, e RITORNA una NUOVA lista con gli elementi rimossi

  • se si richiede di accorciare ad un numero di canzoni superiore al contenuto dell’album, ritorna lista vuota senza modificare l’album

  • USA un nome di parametro diverso da album

  • NON riassegnare album (niente album =)

Esempio:

>>> accorcia(album, 3)  # ritorna
['Sonata Arctica - White Pearl, Black Oceans',
 'Lucio Dalla - 4 marzo 1943.mp3',
 'The Wellermen - Wellerman',
 'Manu Chao - Por el Suelo',
 'Intillimani - El Pueblo Unido']
>>> album               # il parametro è stato modificato
['Caterina Caselli - Cento giorni',
 'Delirium - Jesahel',
 "Jan Hammer - Crockett's Theme"]
>>> accorcia(album, 7)
[]
>>> album
['Caterina Caselli - Cento giorni',
 'Delirium - Jesahel',
 "Jan Hammer - Crockett's Theme"]
Mostra soluzione
[35]:

album = [ "Caterina Caselli - Cento giorni", "Delirium - Jesahel", "Jan Hammer - Crockett's Theme", "Sonata Arctica - White Pearl, Black Oceans", "Lucio Dalla - 4 marzo 1943.mp3", "The Wellermen - Wellerman", "Manu Chao - Por el Suelo", "Intillimani - El Pueblo Unido" ] # scrivi qui

Funzioni lambda

Le funzioni lambda sono funzioni che

  • non hanno un nome

  • sono definite su una sola linea, tipicamente nel posto dove sono necessarie

  • il loro corpo è un espressione, quindi non necessitano della parola chiave return

Proviamo a creare una funzione lambda che prende un numero x e lo raddoppia:

[36]:
lambda x: x*2
[36]:
<function __main__.<lambda>(x)>

Come vedi, Python ha creato un oggetto funzione, che viene mostrato da Jupyter. Sfortunatamente, a questo punto l’oggetto funzione viene perso, perchè questo è il destino di qualcunque oggetto creato da una espressione che non sia assegnato ad una variabile.

Per essere in grado di chiamare la funzione, sarà quindi conveniente assegnare tale oggetto funzione ad una variabile, diciamo f:

[37]:
f = lambda x: x*2
[38]:
f
[38]:
<function __main__.<lambda>(x)>

Ottimo, adesso abbiamo una funzione che possiamo chiamare quante volte vogliamo:

[39]:
f(5)
[39]:
10
[40]:
f(7)
[40]:
14

Di fatto, scrivere

[41]:
def f(x):
    return x*2

oppure

[42]:
f = lambda x: x*2

è completamente equivalente, la differenza principale è che con def possiamo scrivere funzioni con corpi su più linee. Le lambda possono apparire limitanti, perciò perchè usarle? A volte consentono di ottenere codice molto conciso. Per esempio, immagina di avere una lista di tuple contenenti animali e la loro speranza di vita attesa:

[43]:
animali = [('cane', 12), ('gatto', 14), ('pellicano', 30), ('acquila', 25), ('scoiattolo', 6)]

Se vuoi ordinarle per gli anni, puoi provare il metodo .sort ma non funzionerà:

[44]:
animali.sort()
[45]:
animali
[45]:
[('acquila', 25),
 ('cane', 12),
 ('gatto', 14),
 ('pellicano', 30),
 ('scoiattolo', 6)]

chiaramente non abbiamo ottenuto il risultato sperato. Per avere l’ordinamento giusto, dobbiamo dire a Python che al momento di considerare una tupla per fare una comparazione, deve estrarre il numero dell’aspettativa di vita. Per farlo, Python ci fornisce il parametro key, a cui dobbiamo passare una funzione che prende come argomento un elemento della sequenza in considerazione (in questo caso una tupla) e ritorna una trasformazione dello stesso che Python userà per effettuare la comparazione - in questo caso vogliamo gli anni che stanno alla posizione 1-esima della tupla:

[46]:
animali.sort(key=lambda t: t[1])
[47]:
animali
[47]:
[('scoiattolo', 6),
 ('cane', 12),
 ('gatto', 14),
 ('acquila', 25),
 ('pellicano', 30)]

Adesso abbiamo ottenuto l’ordinamento desiderato. Avremmo potuto scrivere la stessa cosa così:

[48]:
def mia_f(t):
    return t[1]

animali.sort(key=mia_f)
animali
[48]:
[('scoiattolo', 6),
 ('cane', 12),
 ('gatto', 14),
 ('acquila', 25),
 ('pellicano', 30)]

ma le lambda chiaramente ci risparmiano caratteri da scrivere.

Nota che le lambda possono prendere più parametri:

[49]:
mia_moltip = lambda x,y: x * y

mia_moltip(2,5)
[49]:
10

Esercizio - applica_bordi

✪ Scrivi una funzione che prende come parametri una funzione f e una sequenza, e RITORNA una tupla con due elementi:

  • il primo elemento è ottenuto applicando f al primo elemento della sequenza

  • il secondo elemento è ottenuto applicando f all’ultimo elemento della sequenza

Esempio:

>>> applica_bordi(lambda x: x.upper(), ['quel', 'fiume', 'è', 'in', 'piena'])
('QUEL', 'PIENA')
>>> applica_bordi(lambda x: x[0], ['quel', 'fiume', 'è', 'in', 'piena'])
('q', 'p')
Mostra soluzione
[50]:
# scrivi qui


[51]:
print(applica_bordi(lambda x: x.upper(), ['quel', 'fiume', 'è', 'in', 'piena']))
print(applica_bordi(lambda x: x[0], ['quel', 'fiume', 'è', 'in', 'piena']))
('QUEL', 'PIENA')
('q', 'p')

Esercizio - processa

✪✪ Scrivi una espressione lambda da passare come primo parametro della funzione definita qua sotto, in modo che una chiamata a processa generi una lista come mostrato qui:

>>> f = METTI_QUI_LA_TUA_FUNZIONE_LAMBDA
>>> processa(f, ['d','b','a','c','e','f'], ['q','s','p','t','r','n'])
['An', 'Bp', 'Cq', 'Dr', 'Es', 'Ft']

NOTA: processa è già definita, non cambiarla

Mostra soluzione
[52]:
def processa(f, lista, listb):
    orda = list(sorted(lista))
    ordb = list(sorted(listb))
    ret = []
    for i in range(len(lista)):
        ret.append(f(orda[i], ordb[i]))
    return ret

# scrivi qui la f = lambda ...


[53]:
processa(f, ['d','b','a','c','e','f'], ['q','s','p','t','r','n'])
[53]:
['An', 'Bp', 'Cq', 'Dr', 'Es', 'Ft']

Continua

Prosegui con gestione errori e testing