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
python

Oltre 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
python
N.B.

Il 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]
python
N.B.

A 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]
python

Con 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]
python

Qui 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]
python
N.B.

La 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']
python

Se 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']
python

Per 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']
python

La 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
python
N.B.

Se 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}
python

Con questa funzione costruttore creiamo una lista di persone:

# Create list of people
people = [Person('Mario', 42), Person('Roberto', 51), Person('Giovanni', 69)]
python

Successivamente 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)
python

L’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)
python

Creazione 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
python

Espressioni 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)
python

Un’altra coppia di parentesi deve contenere l’argomento o gli argomenti:

assert (lambda num: num * num)(3) == 9
python
Consiglio

Per muovere i primi passi, vi consigliamo di consultare il nostro tutorial su Python.

Hai trovato questo articolo utile?
Per offrirti una migliore esperienza di navigazione online questo sito web usa dei cookie, propri e di terze parti. Continuando a navigare sul sito acconsenti all’utilizzo dei cookie. Scopri di più sull’uso dei cookie e sulla possibilità di modificarne le impostazioni o negare il consenso.
Page top