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()omax()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
reducecon 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
Counterper 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.
