Programmazione funzionale in Python: usare <strong>functools.reduce</strong> per aggregazioni eleganti

by theArchitect
SHARE
Programmazione funzionale in Python: usare <strong>functools.reduce</strong> per aggregazioni eleganti
© Guida-HTML5.it

Introduzione

La programmazione funzionale in Python non significa scrivere tutto in stile “puramente funzionale”, ma usare in modo intelligente funzioni, trasformazioni e composizioni per rendere il codice più chiaro, prevedibile e facile da testare. Uno degli strumenti più utili, ma spesso sottovalutati, è functools.reduce().

Questo tutorial si concentra proprio su reduce, un sotto-argomento pratico e molto utile della programmazione funzionale. È particolarmente adatto quando devi aggregare una sequenza di valori in un singolo risultato: sommare, concatenare, combinare dizionari, calcolare prodotti, trovare il massimo personalizzato o costruire strutture dati complesse.

A differenza di map() o delle list comprehension, che trasformano gli elementi uno a uno, reduce() “riduce” una collezione a un unico valore applicando ripetutamente una funzione binaria. È uno strumento potente, ma va usato con criterio: in alcuni casi è perfetto, in altri una semplice sum() o un ciclo for è più leggibile.

Codice completo

from functools import reduce
from operator import add, mul
from collections import Counter


# 1) Somma di una lista di numeri con reduce
numeri = [10, 20, 30, 40]

somma = reduce(add, numeri)
print("Somma:", somma)


# 2) Prodotto di una lista di numeri
valori = [2, 3, 4, 5]

prodotto = reduce(mul, valori)
print("Prodotto:", prodotto)


# 3) Concatenazione di stringhe con separatore
parole = ["python", "funzionale", "reduce"]

frase = reduce(lambda acc, parola: acc + " " + parola, parole)
print("Frase:", frase)


# 4) Unione di dizionari: il secondo sovrascrive le chiavi duplicate
dizionari = [
    {"nome": "Alice", "eta": 30},
    {"citta": "Milano"},
    {"eta": 31, "ruolo": "Developer"}
]

unione = reduce(lambda acc, d: acc | d, dizionari)
print("Dizionario unito:", unione)


# 5) Aggregazione personalizzata: trovare il valore con il punteggio più alto
utenti = [
    {"nome": "Luca", "punteggio": 78},
    {"nome": "Sara", "punteggio": 92},
    {"nome": "Marco", "punteggio": 85}
]

migliore = reduce(
    lambda acc, user: user if user["punteggio"] > acc["punteggio"] else acc,
    utenti
)
print("Utente migliore:", migliore)


# 6) Uso del valore iniziale per gestire sequenze vuote
numeri_vuoti = []

somma_con_iniziale = reduce(add, numeri_vuoti, 0)
print("Somma lista vuota:", somma_con_iniziale)


# 7) Esempio più realistico: conteggio totale di eventi per categoria
eventi = [
    {"tipo": "login", "conteggio": 5},
    {"tipo": "download", "conteggio": 2},
    {"tipo": "login", "conteggio": 3},
    {"tipo": "upload", "conteggio": 4},
    {"tipo": "download", "conteggio": 1},
]

def aggrega_eventi(acc, evento):
    tipo = evento["tipo"]
    conteggio = evento["conteggio"]
    acc[tipo] = acc.get(tipo, 0) + conteggio
    return acc

totali_eventi = reduce(aggrega_eventi, eventi, {})
print("Totali eventi:", totali_eventi)


# 8) Variante funzionale con Counter per il conteggio di occorrenze
parole = ["python", "python", "funzionale", "reduce", "python"]

conteggio = reduce(
    lambda acc, parola: acc + Counter([parola]),
    parole,
    Counter()
)

print("Conteggio parole:", conteggio)

Spiegazione

reduce() riceve una funzione e una sequenza. La funzione deve accettare due argomenti:

  • accumulatore: il risultato parziale calcolato fino a quel momento
  • elemento corrente: il valore successivo della sequenza

Il meccanismo è semplice: reduce prende i primi due elementi, li combina, poi combina il risultato con il terzo, e così via fino alla fine.

1. Somma e prodotto

Nel primo esempio usiamo operator.add invece di una lambda. È una scelta migliore quando esiste una funzione standard già pronta: il codice è più leggibile e spesso più efficiente.

Per il prodotto, operator.mul evita di scrivere una lambda che moltiplica due valori. Questo è un buon esempio di best practice: prima cerca una funzione già disponibile nella libreria standard.

2. Concatenazione di stringhe

Con le stringhe, reduce può concatenare progressivamente i valori. Tuttavia, questo esempio serve soprattutto a capire il concetto: in Python, per unire stringhe, spesso è più leggibile usare " ".join(parole). Quindi qui reduce è didattico, ma non sempre la soluzione migliore.

3. Unione di dizionari

L’esempio con i dizionari è molto pratico. La funzione acc | d crea un nuovo dizionario unendo quello accumulato con il successivo. Se ci sono chiavi duplicate, prevale il valore del dizionario più recente.

Questo pattern è utile quando ricevi configurazioni da più fonti, ad esempio:

  • valori di default
  • configurazione utente
  • override da riga di comando

4. Aggregazione personalizzata

Nel caso degli utenti, reduce serve a trovare il record con il punteggio più alto. Qui la funzione di accumulo confronta due dizionari e restituisce quello migliore.

Questo tipo di logica è utile in scenari come:

  • selezionare il prodotto più venduto
  • trovare il file più recente
  • scegliere il record con priorità maggiore

5. Valore iniziale

Il parametro finale di reduce(), cioè il valore iniziale, è molto importante. Serve a:

  • gestire sequenze vuote senza errori
  • definire il tipo dell’accumulatore
  • rendere il comportamento più prevedibile

Ad esempio, reduce(add, [], 0) restituisce 0 invece di sollevare un’eccezione.

6. Aggregazione con struttura mutabile

Nell’esempio degli eventi, l’accumulatore è un dizionario mutabile. La funzione aggiorna il conteggio per ciascun tipo di evento e restituisce lo stesso dizionario.

Questo approccio è molto utile per creare report, statistiche o riepiloghi. Attenzione però: quando usi strutture mutabili, devi essere coerente e chiaro nel codice, perché gli effetti collaterali possono rendere più difficile il debug.

7. Counter e riduzione

L’ultimo esempio mostra un uso più “funzionale” con collections.Counter. Ogni parola viene trasformata in un piccolo contatore e sommata all’accumulatore. È un esempio interessante, ma in molti casi Counter(parole) è molto più semplice e leggibile.

Questo ci ricorda una regola importante: la programmazione funzionale non è un obiettivo in sé, ma uno strumento per scrivere meglio.

Best practice

  • Usa reduce solo quando il problema è davvero una riduzione: aggregare, combinare o scegliere un singolo risultato.
  • Preferisci funzioni standard come operator.add, operator.mul, sum(), join() o max() quando sono più chiare.
  • Fornisci sempre un valore iniziale se la sequenza può essere vuota o se vuoi controllare il tipo dell’accumulatore.
  • Evita lambda troppo complesse: se la logica cresce, crea una funzione nominata con un nome descrittivo.
  • Non forzare reduce in casi in cui un ciclo for è più leggibile. In Python, la chiarezza conta molto.
  • Attenzione agli effetti collaterali: se l’accumulatore è mutabile, documenta bene il comportamento della funzione.
  • Usa reduce per esprimere intenti chiari: il lettore deve capire subito che stai combinando elementi in un unico risultato.

Riepilogo

functools.reduce() è uno strumento utile della programmazione funzionale in Python quando devi trasformare una sequenza in un singolo valore finale. È perfetto per sommare, moltiplicare, unire, confrontare e aggregare dati in modo compatto.

Abbiamo visto come:

  • usare reduce con funzioni standard e lambda
  • impostare un valore iniziale sicuro
  • aggregare dizionari e strutture dati reali
  • capire quando reduce è utile e quando è meglio scegliere un’alternativa più semplice

Il punto chiave è questo: la programmazione funzionale in Python funziona bene quando migliora la leggibilità, non quando la complica.

Approfondisci con risorse ufficiali

  • Documentazione ufficiale di functools: reduce(), partial() e altri strumenti utili
  • Documentazione ufficiale di operator: funzioni come add, mul, itemgetter
  • Documentazione ufficiale di collections: in particolare Counter per aggregazioni e conteggi
  • PEP 8: linee guida sullo stile del codice Python, utili per mantenere il codice leggibile

Se vuoi, nel prossimo tutorial posso mostrarti un altro sotto-argomento della programmazione funzionale in Python, ad esempio immutabilità e funzioni pure, con esempi pratici e confronto tra approcci funzionali e imperativi.

SHARE