Funzioni anonime (lambda)

Introduzione

Abbiamo visto nella lezione 4 che se si vuole ordinare una lista contenente riferimenti a oggetti di tipo diverso è necessario definire una funzione che li renda comparabili e passare la funzione al metodo sort come suo argomento.

Anche in altri casi può essere necessario passare una funzione al metodo sort. Ad esempio, si supponga di avere una lista di tuple, dove ogni tupla contiene nome, cognome e codice di una persona.

people=[
    ('John', 'Smith', 123),
    ('James', 'Baird', 231),
    ('Rebecca', 'Campbell', 457),
    ('Diane', 'Kelly', 125)
    ]

Se usiamo il metodo sort() sulla lista, otteniamo un ordinamento basato sul primo elemento di ogni tupla:

print('*** ordinamento per nome ***')
people.sort()
showList(people)

*** ordinamento per nome ***
('Diane', 'Kelly', 125)
('James', 'Baird', 231)
('John', 'Smith', 123)
('Rebecca', 'Campbell', 457)

Se vogliamo ordinare le persone per cognome, dobbiamo creare una funzione che estrae il cognome (secondo elemento della tupla) come primo valore da considerare per la comparazione:

def surname_first(data):
    return (data[1], data[0], data[2])

… e poi passare al metodo sort() la funzione definita:

print('*** ordinamento per cognome ***')
people.sort(key=surname_first)
showList(people)

Il risultato è quello che ci aspettiamo:

*** ordinamento per cognome ***
('James', 'Baird', 231)
('Rebecca', 'Campbell', 457)
('Diane', 'Kelly', 125)
('John', 'Smith', 123)

Se volessimo un ordinamento per codice, dovremmo definire un'altra funzione, che questa volta mette al primo posto il terzo elemento della tupla:

def code_first(data):
    return (data[2], data[1], data[0])

e richiamare il metodo sort() con questa nuova funzione:

print('*** ordinamento per codice ***')
people.sort(key=code_first)
showList(people)

*** ordinamento per codice ***
('John', 'Smith', 123)
('Diane', 'Kelly', 125)
('James', 'Baird', 231)
('Rebecca', 'Campbell', 457)

Funzioni anonime

Ovviamente, con l'aumentare del numero dei campi da prendere in considerazione, avremmo bisogno di definire sempre nuove funzioni, il che può costituire un indubbio svantaggio. In queste occasioni, vengono in soccorso le cosiddette funzioni anonime, che si creano al volo quando se ne presenta la necessità.

Ordinamento di una lista

Potremo così risolvere il problema dell'ordinamento in maniera più brillante:

print('*** ordinamento per cognome con funzione lambda ***')
people.sort(key=lambda x: (x[1], x[0], x[2]))
showList(people)

oppure

print('*** ordinamento per codice con funzione lambda ***')
people.sort(key=lambda x: (x[2], x[1], x[0]))
showList(people)

Come si vede in questi esempi, la parola chiave lambda consente di creare una funzione istantaneamente. Il risultato è naturalmente esattamente equivalente:

*** ordinamento per cognome con funzione lambda ***
('James', 'Baird', 231)
('Rebecca', 'Campbell', 457)
('Diane', 'Kelly', 125)
('John', 'Smith', 123)
*** ordinamento per codice con funzione lambda ***
('John', 'Smith', 123)
('Diane', 'Kelly', 125)
('James', 'Baird', 231)
('Rebecca', 'Campbell', 457)

Possiamo in questo modo gestire anche una scelta da parte dell'utente (qui con un minimale controllo di validità):

k=int(input("In base a quale campo vuoi l'ordinamento? "))
print('*** ordinamento su campo a scelta ***')
if not 0<k<3: k=0
people.sort(key=lambda x: x[k])
showList(people)

In base a quale campo vuoi l'ordinamento? 2
*** ordinamento su campo a scelta ***
('John', 'Smith', 123)
('Diane', 'Kelly', 125)
('James', 'Baird', 231)
('Rebecca', 'Campbell', 457)

Le funzioni anonime non supportano i cicli, però supportano le selezioni. Immaginiamo di avere una lista di clienti in cui per ogni cliente è specificato, come quarto campo, se si tratta di una persona fisica o di una persona giuridica ('p' e 'j', rispettivamente):

customers=[
    ('John', 'Smith', 123, 'p'),
    ('James', 'Baird', 231, 'p'),
    ('Acme SA', None, 458, 'j'),
    ('Diane', 'Kelly', 125, 'p'),
    ('Rebecca', 'Campbell', 457, 'p'),
    ('Alphagamma Ltd', None, 318, 'j'),
    ('Blueskies Inc.', None, 941, 'j')
    ]

Se desideriamo che l'ordinamento prenda in considerazione il nome per le persone giuridiche e il cognome per le persone fisiche, potremo scrivere una funzione lambda che contiene una condizione:

customers.sort(key=lambda x: x[0] if x[3]=='j' else x[1])

Il risultato sarà quello che ci attendiamo:

*** ordinamento per nome o cognome con funzione lambda e selezione ***
('Acme SA', None, 458, 'j')
('Alphagamma Ltd', None, 318, 'j')
('James', 'Baird', 231, 'p')
('Blueskies Inc.', None, 941, 'j')
('Rebecca', 'Campbell', 457, 'p')
('Diane', 'Kelly', 125, 'p')
('John', 'Smith', 123, 'p')

Uso di una funzione su un'intera lista

Se vogliamo applicare una funzione a tutti gli elementi della lista, possiamo usare la funzione map(), che restituisce un oggetto iterabile (su cui si può fare un ciclo for, o che si può convertire in lista).

Ad esempio, immaginiamo di volere una lista di tutti i nomi dei nostri clienti (nome e cognome in maiuscolo per le persone fisiche, nome senza nessuna modifica per le persone giuridiche).

Potremmo scrivere un codice come questo:

def capitalizePersonNames(c):
  if c[3]=='p':
    return ' '.join(c[0:2]).upper()
    # mettiamo in maiuscolo i primi due campi, dopo averli
    # uniti con uno spazio
  else:
    return c[0]

names=list(map(capitalizePersonNames, customers))
print(names)

['JOHN SMITH', 'JAMES BAIRD', 'Acme SA', 'DIANE KELLY', 'REBECCA CAMPBELL', 'Alphagamma Ltd', 'Blueskies Inc.']

In alternativa, possiamo definire una funzione anonima, con lo stesso risultato:

names=list(map(
    lambda v: v[0] if v[3]=='j' else ' '.join(v[0:2]).upper(), 
    customers))
print(names)

In maniera analoga, possiamo usare una funzione anonima per filtrare con la funzione filter() gli elementi di una lista in base ad una condizione (nell'esempio qui sotto, vogliamo solo le persone fisiche):

print('*** Elenco delle persone fisiche ***')
people=list(filter(lambda v: v[3]=='p', customers))
print(people)

*** Elenco delle persone fisiche ***
[('John', 'Smith', 123, 'p'), ('James', 'Baird', 231, 'p'), ('Diane', 'Kelly', 125, 'p'), ('Rebecca', 'Campbell', 457, 'p')]

Si noti che usando map() la lista originale non viene modificata. Se desideriamo che effettivamente i valori vengano cambiati, possiamo calcolare una nuova lista. Si consideri il seguente esempio, in cui una lista di prezzi deve essere aggiornata (con la maggiorazione del 10%):

prices=[100.0, 200.0, 340.0, 25.0, 180.0]
print('prezzi di partenza:')
print(prices)
print('prezzi aumentati (calcolati):')
print(list(map(lambda x: round(x*1.1, 2), prices)))
print('i prezzi di partenza non sono cambiati:')
print(prices)

prezzi di partenza:
[100.0, 200.0, 340.0, 25.0, 180.0]
prezzi aumentati (calcolati):
[110.0, 220.0, 374.0, 27.5, 198.0]
i prezzi di partenza non sono cambiati:
[100.0, 200.0, 340.0, 25.0, 180.0]

prices=list(map(lambda x: round(x*1.1, 2), prices))
print('i prezzi ora sono aggiornati:')
print(prices)

i prezzi ora sono aggiornati:
[110.0, 220.0, 374.0, 27.5, 198.0]

Definizione di funzioni tramite espressioni lambda

Può essere comodo, in alcuni contesti, definire una funzione come risultato di un'espressione lambda. Supponiamo ad esempio di voler definire una funzione che calcola l'area di un settore circolare, dato il raggio del cerchio e l'angolo, espresso in gradi.

Come sappiamo, potremmo scrivere la funzione in questo modo:

import math
def sector_area(radius, degrees):
    return radius**2*math.pi*degrees/360

Ma sarebbe esattamente equivalente scrivere anche così:

import math
sector_area = lambda radius, degrees: radius**2*math.pi*degrees/360

In entrambi i casi, potremo chiamare la nostra funzione nel modo seguente:

print(sector_area(3,90))
7.06858347058

Funzioni lambda come gestori di evento

Un caso molto comune di uso di funzioni anonime si ha quando si desidera definire dinamicamente un gestore di evento per widget Tkinter. Consideriamo il caso di una serie di dieci pulsanti per i quali dobbiamo gestire l'evento click. Per dieci pulsanti, dovremmo creare dieci funzioni (con tkinter le funzioni di gestione di eventi non possono avere parametri). Le funzioni anonime, create al volo con l'espressione lambda, ci consentono di superare facilmente la difficoltà:

class Application(object):
    def __init__(self, parent):
        self.parent=parent
        self.buttons=[]

        for i in range(10):
            b=Button(self.parent, text="pulsante %d" %i)
            b['background']='yellow'
            b['command']=self.BuildButtonAction(i)
            b.pack()
            self.buttons.append(b)

    def BuildButtonAction(self, number):
        return lambda : self.ChangeButtonColor(number)

    def ChangeButtonColor(self, number):
        self._SwitchColor(self.buttons[number])

    def _SwitchColor(self, button):
        button['background']='yellow' if button['background']=='red' else 'red'

Lambda GUI

results matching ""

    No results matching ""