Strumenti legati alle funzioni

Il modulo functools mette a disposizione del programmatore Python alcuni strumenti molto utili che possono facilitare il suo lavoro.

Parametri di default con functools.partial()

Immaginiamo di avere una funzione con una lunga serie di parametri obbligatori o con valori di default che non fanno al caso nostro. Partial() ci consente di definire una nuova funzione che ne richiama un'altra indicando gli argomenti che ci interessa fissare, con i rispettivi valori.

In questo esempio, immaginiamo di avere una funzione foo() con i tre parametri a, b e c, e di essere soliti chiamare questa funzione con il parametro b impostato a 10 e con il parametro c a 22 (diverso dal default):

import functools

def foo(a, b, c=0):
    print('a=', a)
    print('b=', b)
    print('c=', c)
    return 1

customfoo=functools.partial(foo, b=10, c=22)

v=customfoo(2)
print(v)

Il risultato sarà:

a= 2
b= 10
c= 22
1

Ovviamente, avremmo potuto raggiungere un risultato analogo anche definendo una funzione wrapper nel modo consueto (ma si noti che così saremmo costretti a definire comunque tutti i parametri, anche quelli per i quali non vogliamo fare nessun cambiamento):

def customfoo(a):
    return foo(a, b=10, c=22)

Gestori di eventi con functools.partial() in Tkinter

La possibilità di definire funzioni in questo modo può essere molto comoda nello sviluppo di applicazioni Tkinter, quando dobbiamo definire gestori di eventi, visto che ci consente di creare le funzioni senza espressioni lambda:

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

        for i in range(5):
            b=Button(self.parent, text="pulsante %d" %i)
            b['background']='yellow'
            b['state']='disabled'
            b['command']=functools.partial(self.ChangeButtonColor, i)
            b.pack()
            self.buttons.append(b)

        self.PlayButton=Button(self.parent,
               text='Gioca',
               command=functools.partial(self.ExecuteCommand, 'play')
                )
        self.PlayButton.pack()
        self.QuitButton=Button(self.parent,
               text='Esci',
               command=functools.partial(self.ExecuteCommand, 'quit')
                )
        self.QuitButton.pack()

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

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

    def ExecuteCommand(self, command):
        if command=='quit':
            self.parent.destroy()
        if command=='play':
            self.StartGame()

    def StartGame(self):
        for button in self.buttons:
            button['state']='active'

Riduzione di una lista con functools.reduce()

Se si ha la necessità di estrarre dati da una lista facendone una sintesi esprimibile in un unico valore (ad esempio, la somma dei valori, la media, ecc.) può essere comodo utilizzare la funzione reduce() del modulo functools.

Immaginiamo di avere una lista contenente le età di alcune persone, e di voler calcolare l'età totale. Possiamo usare functools.reduce a cui passiamo una funzione da usare e la lista su cui lavorare. I due parametri della funzione da usare per la "riduzione" corrispondono rispettivamente al valore del risultato progressivo e al valore dell'elemento considerato:

ages=(22, 24, 21)
print('--- Calcolo somma età con functools.reduce (e funzione lambda) ---')
total_age=functools.reduce(lambda x, y: x+y, ages)
print(total_age)

--- Calcolo somma età con functools.reduce (e funzione lambda) ---
67

Per le operazioni più comuni, il modulo operator ci mette a disposizione le funzioni da usare, in modo da evitare la definizione di una funzione anonima tramite espressione lambda e migliorare la leggibilità del codice:

print('--- Calcolo somma età con functools.reduce (e operator.add) ---')
total_age=functools.reduce(operator.add, ages)
print(total_age)

--- Calcolo somma età con functools.reduce (e operator.add) ---
67

Nel caso di problemi un po' più complessi, è necessario ricorrere a proprie funzioni. Consideriamo il caso di un a tupla di tuple, dove per ogni persona sono presenti nome ed età:

people=(
    ('Alice', 22),
    ('Bob', 24),
    ('Charlie', 21)
    )

Vogliamo sempre sommare le età, ma non possiamo sommare le tuple tra loro, dobbiamo estrarre il secondo valore di ogni tupla.

print('--- Calcolo somma età da una serie di tuple con apposita funzione ---')
def sumUp(a,b):
    # sommma al valore corrente (a) il secondo elemento della tupla b 
    return a+b[1]

total_age=functools.reduce(sumUp, people, 0)
# si noti che in questo caso bisogna inizializzare il sommatore
# (altrimenti viene preso il primo valore, che è una tupla
print(total_age)

--- Calcolo somma età da una serie di tuple con apposita funzione ---
67

Anche in questo caso, naturalmente, sarebbe possibile usare l'operatore lambda:

print('--- Calcolo somma età da una serie di tuple con funzione lambda ---')
total_age=functools.reduce(lambda x, y: x+y[1], people, 0)
print(total_age)

--- Calcolo somma età da una serie di tuple con funzione lambda ---
67

Come ultimo esempio, vediamo il caso del calcolo di una media ponderata, basata su valori e pesi definiti in due tuple "parallele":

values=(10,8,8,9,4)
weights=(1,1,2,3,2)

Tradizionalmente, dovremmo inizializzare a zero un paio di variabili e fare un ciclo per scorrere le tuple e sommare i prodotti e i pesi:

print('--- Calcolo di una media ponderata in maniera tradizionale ---')
ps=0 # somma dei prodotti
ws=0 # somma dei pesi
for v, w in zip(values, weights):
    ps+=v*w
    ws+=w
weighted_average = ps/ws
print(weighted_average)

--- Calcolo di una media ponderata in maniera tradizionale ---
7.66666666667

Attraverso functools.reduce() possiamo fare l'intera operazione in un'unica riga di codice:

print('--- Calcolo di una media ponderata con functools.reduce ---')
weighted_average = functools.reduce(
  operator.add, 
  map(operator.mul, values, weights), 0
  )/functools.reduce(
    operator.add, 
    weights
    )
print(weighted_average)

--- Calcolo di una media ponderata con functools.reduce ---
7.66666666667

Il funzionamento è analogo all'uso delle funzioni MATR.SOMMA.PRODOTTO() e SOMMA() con il foglio elettronico.

results matching ""

    No results matching ""