Tutti i linguaggi di programmazione offrono la possibilità di dichiarare delle funzioni, cioè
dei blocchi di codice separati dal programma principale, che vengono richiamati da esso all'occorrenza. Sappiamo che in
Python la dichiarazione di una funzione si effettua mediante la parola chiave def
. Ad esempio, in un
programma potremmo scrivere:
def quadrato(x):
return x ** 2
. . .
a = quadrato(5)
Python offre però una sintassi alternativa, che ci permette di dichiarare semplici funzioni nel corpo stesso del programma principale, in maniera più compatta. Le funzioni così dichiarate sono dette lambda (il nome deriva dal lambda calculus, un sistema formale introdotto nella logica matematica da Alonso Church).
Apriamo IDLE e scriviamo:
>>> lambda x : x ** 2
<function <lambda> at 0x035B8A50>
Notiamo che IDLE ha riconosciuto la parola chiave lambda
colorandola di arancione, e ci ha risposto dicendo
di aver immagazzinato una function <lambda>
in una certa locazione di memoria. Quindi ora abbiamo una
funzione, ma cosa fa questa funzione e come facciamo a chiamarla? Continuate così (vi ricordo che in IDLE per copiare una
riga già scritta su quella corrente basta spostarsi su di essa e premere <INVIO>).
>>> (lambda x : x ** 2)(5)
25
>>> (lambda x : x ** 2)(12)
144
Usando un po' di intuizione dovreste cominciare a capire: la prima x
(a sinistra dei due punti) è il
parametro formale della funzione, mentre l'espressione x ** 2
a destra è il
valore restituito, cioè il quadrato di x
. Quindi
>>> lambda x : x ** 2
significa "prendi un numero x
e restituisci x ** 2
".
La chiamata alla funzione richiede invece una sintassi un po' più complicata: notiamo che, a differenza delle funzioni
definite con il def
, le lambda
non hanno un nome con cui possono essere chiamate.
Quindi per eseguire una lambda
occorre scrivere tutta la definizione tra parentesi tonde, e poi scrivere di seguito
(sempre tra parentesi, come è richiesto da tutte le funzioni) i parametri reali da passare alla funzione. In pratica nella prima
riga abbiamo chiesto a Python di calcolare il quadrato di 5 e nella terza il quadrato di 12.
Per mezzo delle lambda
Python lascia volutamente la possibilità di dichiarare quelle che nel linguaggio
informatico sono dette anonymous functions, cioè "funzioni senza nome" (ne riparleremo più avanti).
Tuttavia, dato che chiamare una funzione in questo modo è senz'altro complicato, possiamo dare un nome alle
lambda
semplicemente assegnando la loro definizione ad una variabile, in questo modo:
>>> quadrato = lambda x : x ** 2 >>> quadrato(5)
25
>>> quadrato(12)
144
Così la chiamata ad una lambda
diventa del tutto uguale a quella di una funzione
"normale" come quella mostrata nell'esempio iniziale. L'unica differenza è che la lambda
viene dichiarata
"al volo" senza bisogno di una definizione def
staccata dal programma principale.
Una lambda
è una funzione anonima definita dalla sintassi
lambda
argomenti : espressionecioè la parola chiave lambda
, la lista degli argomenti, i due punti ed
un'espressione calcolata a partire dagli argomenti, che sarà il valore restituito dalla funzione.
Notiamo che:
lambda
non è limitata ad un solo argomento (come nell'esempio fatto finora) ma può averne quanti
ne vogliamo: basta scriverli di seguito, separati da virgole;lambda
cicli, istruzioni condizionali o altre istruzioni (vedi, però, il prossimo paragrafo);return
.
>>> func1 = lambda x, y : x + y
>>> func2 = lambda x, y : x + " e " + y + " si amano follemente"
>>> func3 = lambda x : x <= 10
e cercate di capire cosa fa ogni funzione. Chiamatele alcune volte con dei parametri appropriati osservando ogni volta
il risultato restituitolambda somma
che prenda tre parametri e restituisca la loro somma;lambda pari
che prenda come parametro un numero e restituisca True
se il numero è pari,
False
altrimenti (basta usare un'espressione booleana, come nel terzo esempio dell'esercizio
precedente).L'impossibilità di usare le istruzioni if ... else ...
nelle lambda
può essere aggirata per mezzo
delle espressioni condizionali, un costrutto sintattico di Python (in altri linguaggi chiamato
operatore ternario) che permette di scrivere in forma compatta delle semplici espressioni basate su una
condizione. Scriviamo in IDLE:
>>> a = 5 >>> "numero piccolo" if a < 100 else "numero grande"
'numero piccolo'
>>> a = 2000000 >>> "numero piccolo" if a < 100 else "numero grande"
'numero grande'
Un'espressione del tipo
if
condizione else
espressione2è detta espressione condizionale: espressione1 ed espressione2 sono due espressioni (che possono restituire qualsiasi valore) mentre condizione è un'espressione booleana: l'espressione condizionale restituisce espressione1 se condizione è vera, espressione2 se è falsa.
Altri esempi:
>>> 1 if 10 > 5 else 2
1
>>> x = 6 >>> y = 8 >>> x - y if x > y else x + y
14
Questa costruzione ha due vantaggi:
if ... else ...
if ... else ...
che è una serie di istruzioni). Questo permette ad esempio di assegnare il risultato
di un'espressione condizionale ad una variabile:
>>> x = 5 if a < 0 else 10 # supponendo di aver dato prima l'istruzione a = 2000000 >>> x
10
Le espressioni condizionali sono largamente usate nelle lambda
, proprio perchè permettono di simulare un
blocco if ... else ...
che, come abbiamo detto, non si può usare:
>>>func = lambda x : "numero piccolo" if x < 100 else "numero grande" >>>func(10)
'numero piccolo'
>>>func(1000000)
'numero grande'
lambda func4
che accetti un numero e restituisca 5 se il numero è maggiore di 10, 6
altrimentilambda func5
che accetti un numero e restituisca la metà del numero se esso è pari, oppure
il numero diminuito di uno se è dispari;lambda meno0
che accetti due numeri x
e y
e restituisca la loro
differenza se essa è positiva o 0, 0 altrimenti (si può fare in più modi, usando un'espressione
condizionale oppure una funzione built-in di Python).Prima di continuare penso sia necessario soffermarsi un attimo sulla sintassi che usiamo quando chiamiamo una funzione. Se proviamo a scrivere il nome di una funzione di Python senza parentesi ed argomenti vediamo che non otteniamo nessun errore:
<built-in function print>
Questo perchè il nome di una funzione (senza parentesi) è per Python una variabile
come un'altra: può essere una built-in (cioè una funzione predefinita, come la print
dell'esempio), una funzione definita con def
oppure una lambda
a cui abbiamo assegnato un nome.
Quando chiamiamo la funzione Python vede le parentesi tonde della chiamata come un qualsiasi altro operatore,
cioè un simbolo (come +, -, * ecc.) che lega tra loro due o più variabili o costanti (nel nostro caso la funzione e i suoi argomenti)
e restituisce un valore (il risultato della funzione). Quindi possiamo dire che le funzioni sono genericamente
oggetti per i quali è definito l'operatore ()
: questi sono genericamente detti
callable (cioè oggetti chiamabili). Proviamo a fare qualche esperimento in IDLE:
>>> a = 2 >>> b = 3 >>> a + b # operatore + tra due int: OK
5
>>> a(b) # operatore (): errore
Traceback (most recent call last): File "<pyshell#17>", line 1, in
a(b) TypeError: 'int' object is not callable
Abbiamo assegnato alle variabili a
e b
due numeri interi, ed abbiamo usato l'operatore di addizione
che ha restituito la loro somma. Quando abbiamo provato ad usare le parentesi Python ci ha risposto che un int
non è callable e ci ha dato un errore. Ora continuiamo così:
>>> a = lambda x : x ** 2 # ora a e' callable >>> a + b # operatore +: errore
Traceback (most recent call last): File "<pyshell#19>", line 1, in
a + b TypeError: unsupported operand type(s) for +: 'function' and 'int' >>> a(b) # operatore (): OK
9
Ora invece a
contiene un callable (una lambda
): l'operatore + provoca un
errore mentre le () restituisono il risultato della chiamata.
Queste considerazioni saranno molto utili per affrontare il prossimo paragrafo.
Dopo aver imparato ad usare le lambda
potrebbe facilmente venirvi in mente una domanda: ma a
cosa servono? La risposta non è semplicissima: praticamente tutto quello che si può fare con una lambda
potrebbe
essere fatto anche con una normale funzione, e quindi, tranne qualche semplice caso in cui possiamo abbreviare un po' il nostro
codice con qualche funzione definita "al volo", non si vedono grandi applicazioni.
In effetti le funzioni anonime come le lambda
mostrano la loro utilità soprattutto in situazioni abbastanza
complicate e sofisticate, che probabilmente non sono alla portata di un principiante. Il loro uso classico è quello di implementare
le cosiddette high order functions, cioè funzioni che prendono come parametri (oppure restituiscono) altre
funzioni. Per capire di che cosa si tratta facciamo subito un esempio, questa volta usando l'editor:
import math
def fai_qualcosa(f, x):
return f(x)
print(fai_qualcosa(math.sqrt, 4))
Osserviamo che nella riga #4 "chiamiamo" il primo parametro f
dandogli come argomento il
secondo. Questo è perfettamente lecito, perchè come ho detto sopra per Python le parentesi tonde sono un operatore come un altro,
che può essere applicato a due variabili. Ma naturalmente la nostra fai_qualcosa
dovrà essere chiamata con un
callable come primo argomento: osservate la chiamata nella #6: il primo argomento
è il nome della funzione math.sqrt
senza parentesi. Se lanciamo il programma esso stamperà 2 (la radice quadrata
di 4).
Se ora volessimo passare alla funzione fai_qualcosa
una funzione definita da noi, le lambda
diventerebbero utilissime: modificate il programma così:
def fai_qualcosa(f, x):
return f(x)
print(fai_qualcosa(lambda x : 2 * x, 8))
print(fai_qualcosa(lambda x : x + 1, 8)
In pratica potremmo dire noi stessi alla funzione fai_qualcosa
"cosa deve fare" con il secondo argomento,
passandole una funzione come primo argomento. L'alternativa sarebbe quella di scrivere un gran numero di funzioni diverse
e di scegliere quale chiamare concatenando un gran numero di if
; casi come questo sono abbastanza frequenti
nella programmazione di interfacce grafiche, nelle quali l'utente può scegliere tra molte azioni diverse ed il programma
deve interpretare ed eseguire la sua scelta.
Infine vorrei farvi notare che alcune high order functions sono già predefinite in Python. I casi
più semplici sono quelli delle funzioni max, min, sort
, che hanno tutte un parametro opzionale key
che
ci permette di definire la funzione che compara due elementi. Immaginiamo di avere la seguente lista che contiene i dati
di alcune persone sotto forma di dizionario:
persone = [
{"Nome":"Luigi", "Cognome":"Bianchi", "Eta":35},
{"Nome":"Pietro", "Cognome":"Verdi", "Eta":38},
{"Nome":"Laura", "Cognome":"Rossi", "Eta":32},
{"Nome":"Giuseppe", "Cognome":"Neri", "Eta":41}
]
Se cerchiamo di ordinarla otteniamo un errore, perchè Python non permette di confrontare tra loro due dizionari.
>>> persone.sort()
Traceback (most recent call last): File "<pyshell#1>:", line 1, in
persone.sort() TypeError: '<' not supported between instances of 'dict' and 'dict'
Ma possiamo dire alla sort
di confrontare tra loro i cognomi delle persone usando il parametro opzionale
key
(una funzione che restituisca il valore che dobbiamo confrontare):
>>> persone.sort(key=lambda x : x["Cognome"]) >>> persone
[{'Nome': 'Luigi', 'Cognome': 'Bianchi', 'Eta': 35}, {'Nome': 'Giuseppe', 'Cognome': 'Neri', 'Eta': 41}, {'Nome': 'Laura', 'Cognome': 'Rossi', 'Eta': 32}, {'Nome': 'Pietro', 'Cognome': 'Verdi', 'Eta': 38}]
Ecco un'altra applicazione del parametro key
:
>>> nomi = ["Andrea", "Ada", "Massimo", "Luigi", "Valentina"]
>>> nomi.sort(key=len)
>>> nomi
['Ada', 'Luigi', 'Andrea', 'Massimo', 'Valentina']
Questa volta abbiamo ordinato una lista di stringhe per lunghezza. Non abbiamo nemmeno avuto bisogno di una
lambda
perchè avevamo già bell'e pronta la built-in len
.
persone
(anche le funzioni max
e min
hanno il parametro opzionale
key
).
Fine della lezione