Funzioni 1 - introduzione
Scarica zip esercizi
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)
def f(): print('car') f()
def f(): print('car') f()
def f(): return 3 f()
def f(): return 3 f()
def f(): return 3 f()f()
def f(): return 3 f()*f()
def f(): pass f()
def f(): pass print("ciao") f()
def f(x): return x f()
def f(x): return x f(5)
def f(): print('fire') x = f() print(x)
def f(): return(print('fire')) print(f())
def f(x): return 'x' print(f(5))
def f(x): return x print(f(5))
def etc(): print('etc...') return etc() etc()
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:
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.
RITORNA un valore
MODIFICA l’input
MODIFICA l’input e lo RITORNA (permette la concatenazione di chiamate, detto chaining)
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]:
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]:
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]:
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]:
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]:
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_maiusc
che 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]:
Nota alcune cose:
la
stringa_esterna
non è cambiatanon abbiamo scritto
s =
dentro il corpo della funzione, perchè il IV COMMANDMENTO prescrive di non riassegnare i parametrinon 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]:
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]:
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]:
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]:
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]:
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]:
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]:
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]:
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]:
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 parametrinon 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)
def zam(bal): bal = 4 x = 8 zam(x) print(x)
def zom(y): y = 4 y = 8 zom(y) print(y)
def per(la): la.append('è') per(la) print(la)
def zeb(lst): lst.append('d') la = ['a','b','c'] zeb(la) print(la)
def attenzione(la): la = ['?','?'] lb = ['c','a','s','p','i','t','a'] attenzione(lb) print(lb)
def umpa(string): string = "lompa" word = "gnappa" umpa(word) print(word)
def sport(diz): diz['scarpe'] = 2 armadio = {'racchette':4, 'palline': 7} sport(armadio) print(armadio)
def numma(lst): lst + [4,5] la = [1,2,3] print(numma(la)) print(la)
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
[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']
[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 dicanzoniA
NON riassegnare
album
(nientealbum =
)
# 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]
[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
(nientealbum =
)
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']
[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
(nientealbum =
)
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"]
[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 sequenzail 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')
[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
[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