Lambda in Python: le funzioni anonime in Python
Le funzioni lambda esistono sin da Python 1.0 come mezzo per la programmazione funzionale. Oggi il loro utilizzo è stato sostituito in larga parte da altre tecniche. Tuttavia vi sono ancora alcuni ambiti specializzati in cui i programmatori Python più esperti dovrebbero saperle utilizzare.
Che cosa sono le funzioni lambda in Python?
Con il termine “funzione lambda” in Python si intende una funzione anonima. Per creare una funzione lambda in Python si utilizza la parola chiave lambda
. Un’espressione lambda è composta dalla parola chiave lambda
seguita da un elenco di argomenti, dai due punti e da una singola espressione (“expression”). Richiamando la funzione lambda, l’espressione viene dotata degli argomenti e valutata:
lambda argument: expression
Le funzioni sono un costrutto linguistico fondamentale di quasi tutti i linguaggi di programmazione e costituiscono la minima unità di codice riutilizzabile. Di solito le funzioni in Python sono definite con la parola chiave def
. Come esempio, vi mostriamo la funzione di Python che eleva un numero al quadrato moltiplicandolo per sé stesso:
# Define square function
def square(num):
return num * num
# Show that it works
assert square(9) == 81
pythonOltre al noto metodo usato per definire le funzioni in Python con la parola chiave def
, il linguaggio di programmazione dispone anche delle “lambda”. Si tratta di brevi funzioni anonime (ossia a cui non è stato assegnato un nome) che definiscono un’espressione con dei parametri. Le lambda si utilizzano in tutti i casi in cui è prevista una funzione oppure si collegano a un nome tramite assegnazione. Questa è l’espressione lambda equivalente alla funzione per elevare al quadrato:
# Create square function
squared = lambda num: num * num
# Show that it works
assert squared(9) == 81
pythonIl termine “funzione lambda” indica in Python una funzione creata con la parola chiave lambda. Lambda non è il nome di una funzione e non si tratta di un operatore Python.
Qual è la differenza fra lambda e def?
A prima vista può sembrare strano che in Python vi siano due modi per creare le funzioni, ossia con lambda
e con def
. In realtà, lambda
non è un metodo vero e proprio, ma soltanto una diversa sintassi con cui scrivere brevi funzioni in locale. Infatti, ogni funzione creata con lambda
può essere creata anche con def
. Tuttavia, non è vero il contrario.
A livello sintattico, sia lambda
che def
sono parole chiave. Una differenza fra le due funzionalità sta nella rigorosa distinzione tra istruzione (“statement”) ed espressione (“expression”) in Python. In poche parole, le istruzioni sono fasi di esecuzione del codice, mentre le espressioni sono valutate per restituire un valore.
La parola chiave def
indica l’inizio di un’istruzione (nello specifico, un’“istruzione composta”) che contiene ulteriori istruzioni. All’interno dell’istruzione def
(e solo qui) possono esservi delle istruzioni return
. Richiamando la funzione definita con def
, un’istruzione return
restituisce un valore.
A differenza dell’istruzione def
, la parola chiave lambda
indica l’inizio di un’espressione che non può contenere istruzioni. L’espressione lambda riceve uno o più argomenti e restituisce una funzione anonima. Richiamando la funzione generata con lambda, l’espressione che contiene viene valutata con gli argomenti indicati e quindi restituita.
Quali sono le limitazioni delle espressioni lambda in Python?
Python limita appositamente l’utilizzo delle funzioni lambda, in quanto di norma è meglio assegnare un nome alle funzioni. Questa scelta obbliga i programmatori a riflettere sul significato della funzione e a separare i vari componenti in modo chiaro l’uno dall’altro.
A differenza del corpo di una funzione definita con la parola chiave def
, le lambda non possono contenere alcuna istruzione. Pertanto non è impossibile utilizzare if
, for
ecc. all’interno di una funzione lambda. Analogamente, non è possibile sollevare un’eccezione, in quanto per farlo serve l’istruzione raise
.
Le funzioni lambda in Python possono contenere soltanto una singola espressione, che viene valutata quando viene richiamata. All’interno dell’espressione lambda non è possibile utilizzare annotazioni di tipo. Oggi per la maggior parte delle applicazioni delle funzioni lambda in Python si utilizzano altre tecniche. Al riguardo, vale la pena menzionare in particolare le comprensioni.
Per cosa si utilizzano le funzioni lambda in Python?
In generale, le lambda provengono dalla programmazione funzionale. In alcuni linguaggi, come JavaScript, si utilizzano spesso le funzioni anonime senza ricorrere a una particolare parola chiave. In Python, le espressioni lambda servono a creare piccole funzioni in locale senza troppi fronzoli. Nelle parti seguenti vi spieghiamo le loro applicazioni più utili.
Utilizzo di funzioni di ordine superiore in Python con le lambda
Spesso si utilizzano le lambda in connessione con funzioni di ordine superiore, come map()
, filter()
e reduce()
. Grazie alle lambda è possibile trasformare gli elementi di un “iterabile” senza l’uso di cicli. Per funzioni di ordine superiore (in inglese, “higher-order functions”) si intendono funzioni che assumono altre funzioni come parametri oppure che restituiscono una funzione.
La funzione map()
assume come parametro una funzione e un iterabile ed esegue quindi la funzione per ciascun elemento dell’iterabile. Consideriamo il problema di generare numeri al quadrato: utilizziamo la funzione map()
e assegniamo un’espressione lambda come argomento che genera la funzione di elevamento al quadrato. Con map()
si applica la funzione di elevamento al quadrato a ogni elemento della lista:
nums = [3, 5, 7]
# Square numbers using using `map()` and `lambda`
squares = map(lambda num: num ** 2, nums)
# Show that it works
assert list(squares) == [9, 25, 49]
pythonA partire da Python 3.0, le funzioni map()
e filter()
restituiscono un iterabile anziché una lista. All’interno dell’istruzione assert
utilizziamo una chiamata list()
per spacchettare l’iterabile ottenendo una lista.
Le comprensioni di lista offrono oggi un approccio moderno e preferibile per l’elaborazione di iterabili. Anziché ricorrere a map()
e creare una funzione lambda, è possibile descrivere l’operazione direttamente:
nums = [3, 5, 7]
# Square numbers using list comprehension
squares = [num ** 2 for num in nums]
# Show that it works
assert squares == [9, 25, 49]
pythonCon la funzione filter()
è possibile filtrare gli elementi di un iterabile. Allarghiamo il nostro esempio in modo da generare solo numeri al quadrato pari:
# List of numbers 1–4
nums = [1, 2, 3, 4]
# Square each number
squares = list(map(lambda num: num ** 2, nums))
# Filter out the even squares
even_squares = filter(lambda square: square % 2 == 0, squares)
# Show that it works
assert list(even_squares) == [4, 16]
pythonQui mostriamo nuovamente l’approccio moderno e preferibile per generare lo stesso risultato senza uso di lambda e funzioni di ordine superiore per mezzo delle comprensioni di lista. A tal fine usiamo il componente if
della comprensione per filtrare i numeri al quadrato pari:
# List of numbers 1–4 squared
squares = [num ** 2 for num in range(1, 5)]
# Filter out the even squares
even_squares = [square for square in squares if square % 2 == 0]
# Show that it works
assert even_squares == [4, 16]
pythonLa funzione reduce()
di Python non fa più parte della libreria standard a partire da Python 3.0. Ora si trova nel modulo functools
.
Realizzazione di funzioni chiave in Python sotto forma di lambda
Le comprensioni hanno sostituito in larga parte l’uso delle classiche funzioni di ordine superiore map()
e filter()
in Python. Con le cosiddette “key functions”, in italiano “funzioni chiave”, esiste comunque uno scenario applicativo in cui le lambda mostrano tutto il loro potenziale.
Le funzioni di confronto Python sorted()
, min()
e max()
operano su iterabili. Alla chiamata, ciascun elemento dell’iterabile viene sottoposto a un confronto. Le tre funzioni assumono tutte una funzione chiave sotto forma di parametro key
opzionale. La funzione chiave è chiamata per ciascun elemento e restituisce un valore chiave per l’operazione di confronto.
Come esempio, consideriamo il problema seguente. È presente una cartella contenente file di immagini, i cui nomi sono riportati in una lista di Python. L’obiettivo è mettere in ordine la lista. I nomi dei file iniziano tutti per img
, seguito da una numerazione:
# List of image file names
images = ['img1', 'img2', 'img30', 'img3', 'img22', 'img100']
pythonSe usiamo la funzione sorted()
di Python, si utilizza il cosiddetto “ordine lessicografico”. In pratica, la funzione gestisce le cifre consecutive come singoli numeri. In tal modo, i numeri ['1', '2', '100']
vengono ordinati come ['1', '100', '2']
. Il risultato non corrisponde alle nostre aspettative:
# Sort using lexicographic order
sorted_image = sorted(images)
# Show that it works
assert sorted_image == ['img1', 'img100', 'img2', 'img22', 'img3', 'img30']
pythonPer ottenere l’ordine desiderato, creiamo un’espressione lambda
che genera una funzione chiave. La funzione chiave estrae la componente numerica di un nome del file, che viene quindi utilizzata da sorted()
come chiave:
# Extract numeric component and sort as integers
sorted_image = sorted(images, key=lambda name: int(name[3:]))
# Show that it works
assert sorted_image == ['img1', 'img2', 'img3', 'img22', 'img30', 'img100']
pythonLa funzione chiave viene utilizzata solo in locale e una volta sola. Non è necessario definire appositamente un’ulteriore funzione dotata di nome. Le lambda sono quindi lo strumento giusto per creare funzioni chiave. Consideriamo altri due esempi.
Oltre a sorted()
, le funzioni min()
e max()
integrate in Python assumono una funzione chiave opzionale. Le funzioni individuano l’elemento più piccolo e/o più grande in una lista o in un altro iterabile. Cosa sia esattamente l’elemento più piccolo e/o più grande dipende dalla definizione ed è quindi definibile per mezzo della funzione chiave.
Nelle liste che contengono valori semplici, ad esempio una lista di numeri, è chiaro che cosa si intenda per “più piccolo” e “più grande”. In questo caso non serve alcuna funzione chiave speciale:
nums = [42, 69, 51, 13]
assert min(nums) == 13
assert max(nums) == 69
pythonSe non si assegna nessuna funzione chiave, si utilizza implicitamente la funzione identità f(x) = x
. Quest’ultima è facilmente definibile come funzione lambda di Python con lambda x: x
.
Cosa succede invece se ciascuno degli elementi di un iterabile comprende più dati? Immaginiamo di avere una lista di dict che rappresentano le persone con nome ed età. Quale criterio devono usare min()
e max()
per stabilire qual è l’elemento più piccolo e/o più grande? La funzione chiave serve proprio a questo.
Per capire meglio come funzionano le funzioni chiave servono dei dati di esempio. Creiamo una funzione Person()
che funge da costruttore:
# Constructor function for dict representing a person
def Person(name, age):
return {'nome': name, 'età': age}
# Check that it works as expected
assert Person('Mario', 42) == {'nome': 'Mario', 'età': 42}
pythonCon questa funzione costruttore creiamo una lista di persone:
# Create list of people
people = [Person('Mario', 42), Person('Roberto', 51), Person('Giovanni', 69)]
pythonSuccessivamente troviamo la persona più vecchia per mezzo della chiamata max()
. A tal fine usiamo l’espressione lambda per creare una funzione chiave che elabora un dict di una persona e ne estrae l’età come elemento per il confronto:
# Find the oldest person
oldest = max(people, key=lambda person: person['età'])
# Check that it works
assert oldest == Person('Giovanni', 69)
pythonL’approccio funziona esattamente allo stesso modo anche per la funzione min()
. In questo caso definiamo la funzione chiave al di fuori della chiamata min()
utilizzando nuovamente un’espressione lambda. Questa soluzione migliora la leggibilità ed è utile quando si utilizza la funzione chiave più volte in locale:
# Define key function to compare people by age
by_age = lambda person: person['age']
# Find the youngest person
youngest = min(people, key=by_age)
# Check that it works
assert youngest == Person('Mario', 42)
pythonCreazione di “closure” con le lambda in Python
Un ulteriore utilizzo per le lambda in Python è la definizione delle cosiddette “closure”. Si tratta di funzioni generate da altre funzioni e che memorizzano un valore. Le “closure” permettono ad esempio di creare famiglie di funzioni simili. Vi mostriamo l’esempio più comune per la creazione di funzioni di potenza.
Le funzioni di potenza assumono un argomento e lo elevano a potenza. Esempi noti sono la funzione di elevamento al quadrato f(x) = x ^ 2
e la funzione di elevamento al cubo f(x) = x ^ 3
. Tramite una funzione costruttore è possibile creare funzioni di potenza a piacere sotto forma di “closure”. Utilizziamo un’espressione lambda evitando quindi di definire una funzione interna dotata di un nome:
# Define constructor function for power functions
def power(n):
return lambda num: num ** n
# Create square and cubic functions as closures
square = power(2)
cubic = power(3)
# Show that it works
assert square(10) == 100
assert cubic(10) == 1000
pythonEspressioni IIFE (Immediately Invoked Function Expression) con le lambda in Python
Le IIFE (pronuncia: “iffy”) sono un modello ben noto in JavaScript. Con questo modello si definisce una funzione anonima, che viene eseguita immediatamente.
Anche se le limitazioni in Python le rendono poco utili, le lambda sono utilizzabili come IIFE. È sufficiente mettere l’espressione lambda fra parentesi:
(lambda num: num * num)
pythonUn’altra coppia di parentesi deve contenere l’argomento o gli argomenti:
assert (lambda num: num * num)(3) == 9
pythonPer muovere i primi passi, vi consigliamo di consultare il nostro tutorial su Python.