Vai al contenuto principale

Python reduce(): Guida completa

Scopri quando e come usare reduce() in Python. Include esempi pratici e buone pratiche.
Aggiornato 29 set 2026  · 11 min leggi

Scopri con l'IA

ChatGPTClaudePerplexity

La funzione reduce() di Python arriva dal mondo della programmazione funzionale. La programmazione funzionale (FP) è un paradigma in cui i programmi producono risultati applicando funzioni a dati immutabili. 

Un pattern comune in questo stile è il "fold", che comprime una sequenza in un unico risultato. Per esempio, pliare la lista di numeri [2, 4, 5, 3] con l'addizione produce 14 tramite passaggi successivi: [2, 4, 5, 3] → [6, 5, 3] → [11, 3] → 14.

reduce() generalizza questa idea. Applica un'operazione binaria su un iterabile finché resta solo il risultato.

In questo articolo esplorerò gli elementi chiave di reduce() in Python e presenterò alcuni esempi pratici. Se ti serve un ripasso delle basi di Python, ti consiglio queste risorse:

Storia

Python include altre funzioni di programmazione funzionale, come map() e filter(). 

Costrutti funzionali come map() e filter() erano presenti in Python 1.0. Guido van Rossum non li amava, sottolineando che reduce() era difficile da leggere e che un ciclo for è quasi sempre più chiaro.  In Python 3.0, seguendo la PEP 3100, gli sviluppatori hanno rimosso reduce() dai built-in e l'hanno spostata nel modulo functools. Questo passaggio a functools l'ha sostanzialmente declassata a strumento di nicchia.

Perché usare reduce()?

La maggior parte delle volte, trovo che sia probabilmente meglio usare un built-in o un ciclo. Tuttavia, reduce() resta una buona scelta in alcuni casi d'uso.

  • Pipeline di funzioni. Collega in catena una serie (anche dinamica) di trasformazioni in modo pulito.
  • Fold algebrici. Utile per operazioni con valori identitari naturali, come l'unione di insiemi con l'insieme vuoto o operazioni di bitmask con zero.
  • Fold personalizzato senza built-in. Definisci una fusione su misura per il dominio quando non esiste un built-in.
  • Accumulatori strutturati. Traccia più stati contemporaneamente all'interno di un'unica funzione accumulatrice personalizzata.

Come funziona reduce() in Python

Vediamo la meccanica di reduce().

La firma di base di reduce() è 

functools.reduce(function, iterable, [initializer])

La funzione reduce() accetta due argomenti obbligatori e un terzo opzionale.

  • function: una funzione binaria che specifica come combinare due elementi,
  • iterable: la sequenza o l'iterabile da ridurre, come una lista o una tupla, initializer (opzionale): un valore iniziale per avviare la funzione.

Esempio passo dopo passo:

  •  [1, 3, 2, 7] → [4, 2, 7].
  • [4, 2, 7] → [6, 7].
  • [6, 7] → [13].

Il risultato finale è 13.

Esempi semplici di reduce()

I seguenti esempi didattici mostrano la meccanica di utilizzo di reduce(). 

from functools import reduce

numbers = [2, 4, 6]
product = reduce(lambda x, y: x * y, numbers)  # ((2 x 4) x 6) = 8 x 6 = 48
min_value = reduce(lambda x, y: x if x < y else y, numbers) # 2

words = ['dog', 'cat', 'tree', 'pony']
str_concat = reduce(lambda x, y: x + y, words)  # "dogcattreepony"

Initializer

Senza un initializer, reduce() prende il primo elemento dell'iterabile come valore iniziale. Se l'iterabile è vuoto, reduce() lancia un TypeError. Per rendere il codice robusto, fornisci un initializer per definire il comportamento su input vuoti.

from functools import reduce

words = []
# Error: reduce() of empty iterable with no initial value
str_concat = reduce(lambda x, y: x + y, words)

# Correct: use empty string initializer
str_concat = reduce(lambda x, y: x + y, words, "")

Gli initializer servono anche per inizializzare il risultato con un contenitore vuoto. Ad esempio, puoi concatenare parole in una lista piatta di caratteri. 

from functools import reduce

words = ['reduce', 'is', 'fun']
chars_list = reduce(lambda acc, word: acc + list(word), words, [])
print(chars_list)  # ['r', 'e', 'd', 'u', 'c', 'e', 'i', 's', 'f', 'u', 'n']

Per esplorare ancora Python e la manipolazione dei dati, ecco alcune opzioni che consiglio.

Definire il reducer

Finora abbiamo usato funzioni lambda per definire l'operatore binario. 

Puoi anche usare operatori dal modulo operator.  Il modulo operator contiene versioni funzionali di operatori comuni e chiamate a metodi. Per esempio, invece di x + y, operator.add(x, y). Questo ti consente di passare operatori predefiniti (ed efficienti) a reduce() senza dover scrivere una lambda.

from functools import reduce
import operator as op

numbers = [2, 4, 6]
total = reduce(op.add, numbers, 0)  # instead of reduce(lambda x,y: x + y, numbers)

Una terza opzione è scrivere una funzione personalizzata. È una buona scelta quando non esiste un operatore predefinito o la funzione è troppo complessa per una lambda. 

Per esempio, supponi di voler rimuovere i duplicati da una lista, mantenendo l'ordine della prima occorrenza. Potresti definire un reducer che aggiunge un elemento a una lista solo se non è già apparso in precedenza.  

from functools import reduce


items = ['the', 'wild', 'wild', 'world', 'is', 'the', 'wide', 'world', 'is', 'the', 'world']


def dedup(acc, x):
    if x not in acc:  # O(n) membership test
        acc.append(x)
    return acc




unique = reduce(dedup, items, [])  # ['the', 'wild', 'world', 'is', 'wide']

Per ulteriori idee sulla rimozione dei duplicati, dai un'occhiata a questo tutorial.

Prestazioni di reduce() in Python

reduce() comporta questioni di performance.

Overhead delle chiamate di funzione

Python reduce() chiama la nostra funzione una volta per ogni elemento. Su una lista con un milione di elementi, significa un milione di chiamate di funzione, ognuna delle quali crea un frame, gestisce gli argomenti e aggiorna i reference count. Questo aggiunge un notevole overhead.

Al contrario, un built-in come sum effettua una singola chiamata a una funzione C ed esegue il milione di addizioni all'interno del ciclo in C. Questa differenza può rendere il built-in di ordini di grandezza più veloce.

Località della cache ed efficienza della CPU

Reduce soffre anche in termini di località della cache ed efficienza della CPU.  

Una CPU moderna può eseguire diversi miliardi di istruzioni al secondo, ma l'accesso alla RAM è di ordini di grandezza più lento. Per compensare, le CPU moderne hanno cache (L1, L2, L3) che memorizzano dati per un accesso rapido.

Queste cache sfruttano due pattern. 

  • Località temporale: i dati usati di recente probabilmente verranno riutilizzati. 
  • Località spaziale: i dati vicini a un indirizzo usato di recente probabilmente verranno usati a breve.

Al contrario, ogni step di reduce() comporta inseguire puntatori per trovare l'elemento successivo e chiamate di funzione Python, che rompono la località e bloccano la CPU. I built-in e le funzioni vettorizzate evitano il problema eseguendo cicli C compatti.

Per ripassare come scrivere codice Python idiomatico ed efficiente, dai un'occhiata a:

Alternative a reduce() in Python

Built-in:

  • Codice C ottimizzato. I built-in eseguono i loro cicli in C ottimizzato, non in Python. Ciò evita l'overhead che reduce() introduce. Questo vantaggio si amplifica su input grandi. 
  • Leggibilità. I built-in hanno nomi descrittivi (sum, min) quindi il loro intento è ovvio. Una chiamata a reduce() ti costringe a interpretare la funzione passata.

Cicli:

  • Performance. I cicli in genere sono più lenti dei built-in ma più veloci di reduce().
  • Leggibilità. Come i built-in, i cicli sono di solito più leggibili di reduce(). Una chiamata a reduce() ti obbliga a interpretare un enunciato funzionale, mentre un ciclo è più "pythonic".

itertools.accumulate()

La libreria itertools di Python offre una raccolta di iteratori performanti come count(), product() e combinations(). Una funzione utile di itertools è itertools.accumulate(). Come reduce(), applica una funzione a un iterabile. Tuttavia, accumulate() memorizza i valori intermedi del calcolo, non solo il risultato finale. 

Per esempio,

import itertools, operator
from functools import reduce

list(itertools.accumulate([1, 2, 3, 4], initial=0)) # [0, 1, 3, 6, 10]
reduce(operator.add, [1, 2, 3, 4], 0) # 10

Accumulate è utile quando ti servono totali progressivi o minimi/massimi progressivi. Ad esempio, potresti voler conoscere mese per mese la temperatura massima.

Errori comuni nell'uso di reduce()

Quando usi reduce(), tieni a mente i seguenti errori comuni.

  • Preferisci alternative più semplici come built-in o cicli. Riserva reduce() a quando ti serve davvero.
  • Gestisci gli iterabili vuoti. Usa sempre un initializer appropriato per evitare errori su input vuoti.
  • Attenzione alla memoria. Non forzare reduce() in situazioni dove un approccio con generatori o in streaming sarebbe più efficiente.
  • Evita lambda complicate. Usa le funzioni del modulo operator quando puoi. Le lambda, soprattutto con operazioni non associative, possono ridurre la chiarezza.
  • Privilegia la chiarezza rispetto all'astuzia.

Buone pratiche e linee guida per reduce() in Python 

Come per qualsiasi strumento, ci sono buone pratiche per reduce(). Se hai deciso che reduce() è lo strumento giusto, ecco alcune linee guida per il suo utilizzo.

Progetta prima il reducer 

  • Definisci il contratto in una frase. "Combina le chiavi del dizionario sommando i conteggi per chiave."
  • Mantienilo associativo se possibile. Questo ti permette di parallelizzare e testare più facilmente.
  • Identifica l'elemento identitario e usalo come initializer. Ad esempio, per la somma l'identità è 0. Per min è math.inf, e per set union è set(). Questo mantiene il codice robusto e libero da TypeError.

Tienilo semplice e leggibile

  • Un'operazione chiara. Nessun effetto collaterale.
  • Dai al reducer un nome descrittivo.

Documenta

  • Nella docstring, annota l'elemento identitario e il comportamento su input vuoti, l'assunzione di associatività, la policy sugli errori.

Test

  • Test unitari sui casi limite: iterabile vuoto, tipi misti, valori estremi.
  • Verifica l'associatività.

Monitora le prestazioni

  • Esegui benchmark su input piccoli e grandi. Confronta i risultati con built-in e cicli.
  • Se la velocità conta, valuta pre-processing, batching e lo spostamento della matematica pesante su NumPy o pandas.

Applicazioni avanzate e casi d'uso reali per reduce() in Python

Visti gli svantaggi, potrebbe sembrare che reduce() non abbia un vero valore. Al contrario, reduce() ha molti casi d'uso utili. 

  • Elaborazione di strutture annidate
  • Operazioni in stile database
  • Pipeline di elaborazione dati
  • Applicazioni MapReduce

Elaborazione di strutture annidate

Reduce offre un modo pulito per attraversare strutture dati annidate, come oggetti JSON, effettuando una sequenza di lookup tramite una lista di chiavi.

import json
from functools import reduce
import operator

data = json.loads('''
{
  "user": {
    "id": "ABC123",
    "name": "Alice",
    "email": "alice@example.com",
    "profile": {
      "address": {
        "city": "San Francisco",
        "zip": "94103"
      },
      "age": 34,
      "skills": ["Python", "Data Science", "Machine Learning"]
    }
  }
}
''')

# Example lookups with reduce + operator.getitem
city = reduce(operator.getitem, ["user", "profile", "address", "city"], data)
print(city)  # "San Francisco"

user_id = reduce(operator.getitem, ["user", "id"], data)
print(user_id)  # "ABC123"

age = reduce(operator.getitem, ["user", "profile", "age"], data)
print(age)  # 34

Usare reduce() ha senso qui. Il JSON è profondamente annidato: user → profile → address → city. Invece di concatenare i lookup manualmente, rappresenta il percorso come una lista di chiavi. Poi usa reduce(operator.getitem, path, data) per attraversarlo. Questo mantiene il codice generico, leggibile e riutilizzabile.

Pipeline di elaborazione dati

Reduce può gestire pipeline di elaborazione dati passando i dati attraverso una sequenza di trasformazioni. Ogni funzione gestisce un singolo step e la pipeline risulta dall'applicarle in ordine. Ecco una pipeline didattica che pre-elabora una stringa di testo prima di passarla a un modello NLP. 

from functools import reduce
import re

# Define preprocessing steps
def strip_punctuation(s):
    return re.sub(r"[^\w\s]", "", s)

def to_lower(s):
    return s.lower()

def remove_stopwords(s):
    stops = {"the", "is", "a", "of"}
    return " ".join(word for word in s.split() if word not in stops)

def stem_words(s):
    # trivial "stemmer": cut off 'ing'
    return " ".join(word[:-3] if word.endswith("ing") else word for word in s.split())

pipeline = [
    strip_punctuation,
    to_lower,
    remove_stopwords,
    stem_words,
]

# Input data
text = "The quick brown fox is Jumping over a log."

# Apply pipeline with reduce
processed = reduce(lambda acc, f: f(acc), pipeline, text)
print(processed)  # quick brown fox jump over log

Gestione degli errori in applicazioni complesse

Torniamo all'esempio del JSON annidato. Al momento, la chiamata diretta a reduce(operator.getitem, …) genera un KeyError o un TypeError se manca una chiave o se incontra un non-dizionario. Per rendere il codice più sicuro, definisci una funzione helper che incapsula operator.getitem in un blocco try/except e restituisce un valore di default in caso di errore.

Ecco una possibile funzione helper.

def deep_get(data, keys, default=None):
    """Traverse nested dicts/lists safely with reduce."""
    try:
        return reduce(operator.getitem, keys, data)
    except (KeyError, IndexError, TypeError):
        return default

Ora modifica i lookup di esempio per usare la nostra nuova funzione invece di un reduce() non incapsulato

# Example lookups
city = deep_get(data, ["user", "profile", "address", "city"], default="Unknown City")
print(city)  # "San Francisco"

user_id = deep_get(data, ["user", "id"], default="N/A")
print(user_id)  # "ABC123"

age = deep_get(data, ["user", "profile", "age"], default="N/A")
print(age)  # 34

# Example with missing key
phone = deep_get(data, ["user", "profile", "phone"], default="No phone")
print(phone)  # "No phone"

Trasformazioni dati multi-step con map() e filter()

Puoi combinare reduce() con altri strumenti funzionali, come map() e filter(), per costruire trasformazioni dati multi-step.  Ecco la nostra precedente pipeline di pre-processing NLP riscritta in stile funzionale.

from functools import reduce
import re

# Define preprocessing steps
def strip_punctuation(s):
    return re.sub(r"[^\w\s]", "", s)

def to_lower(s):
    return " ".join(map(str.lower, s.split()))

def remove_stopwords(s):
    stops = {"the", "is", "a", "of"}
    return " ".join(filter(lambda w: w not in stops, s.split()))

def stem_words(s):
    # trivial "stemmer": cut off 'ing'
    return " ".join(map(lambda w: w[:-3] if w.endswith("ing") else w, s.split()))

# Pipeline of transformations
pipeline = [
    strip_punctuation,
    to_lower,
    remove_stopwords,
    stem_words,
]

# Input data
text = "The quick brown fox is Jumping over a log."

# Apply pipeline with reduce
processed = reduce(lambda acc, f: f(acc), pipeline, text)
print(processed)  # quick brown fox jump over log

Le trasformazioni sono:

  • Rimozione della punteggiatura. "The quick brown fox is Jumping over a log." → "The quick brown fox is Jumping over a log"
  • Conversione in minuscolo

"The quick brown fox is Jumping over a log" → "the quick brown fox is jumping over a log"

  • Rimozione delle stop word. "the quick brown fox is jumping over a log" → "quick brown fox jumping over log"
  • Stemming delle parole

 "quick brown fox jumping log"  → "quick brown fox jump log"

Per approfondire programmazione funzionale e vettorizzazione, consigliamo questi articoli di DataCamp.

Integrazione con l'ecosistema Python moderno

Per usare bene reduce(), aiuta capire come si inserisce nell'ecosistema Python moderno. Vediamo come si affianca a NumPy e pandas, come è alla base di sistemi paralleli e distribuiti e come interagisce con strumenti moderni come gli analyzer statici. 

numpy e pandas

NumPy e pandas lavorano con codice C ottimizzato, quindi non duplicare la loro funzionalità con reduce(). Tuttavia, reduce() è una buona scelta per pipeline con step dinamici. Per esempio, potresti comporre molte trasformazioni NumPy su un array.

from functools import reduce
import numpy as np

def standardize(x):
    return (x - x.mean()) / (x.std() + 1e-9)

def clip(x):
    return np.clip(x, 0, 1)

def log(x):
    return np.log1p(x)

x = np.array([1, 500, 40.5, 100, 250.45])
funcs = [standardize, clip, log]
y = reduce(lambda a, f: f(a), funcs, x)  # x is ndarray

Framework per il calcolo parallelo e distribuito

Reduce è centrale nei framework di calcolo parallelo e distribuito. Questi sistemi dividono un dataset in partizioni, elaborano tali partizioni in parallelo, quindi combinano i risultati parziali in un'unica risposta. Lo step di "combine" è la riduzione.

Perché la riduzione funzioni correttamente su un cluster, assicurati che siano rispettate le seguenti condizioni.

  • Associatività. L'operazione deve dare lo stesso risultato indipendentemente dal raggruppamento. Questo consente di unire i risultati parziali in qualsiasi ordine attraverso la rete.
  • Elemento identitario. L'operazione deve avere un initializer che non influenzi il risultato finale. Questo garantisce correttezza quando le partizioni sono vuote o di dimensioni diseguali. 

Se queste proprietà non valgono, le riduzioni diventano lente (perché non possono essere parallelizzate in sicurezza) o scorrette (perché i risultati dipendono dall'ordine di valutazione).

Questo processo richiede un'operazione associativa e un'identità chiara. Altrimenti, ottieni codice lento o risultati non corretti.

Strumenti di analisi statica

Uno strumento di analisi statica (come mypy, Pyright, ruff o bandit) ispeziona il codice senza eseguirlo. Questi strumenti intercettano bug, impongono regole di stile e verificano la correttezza dei tipi.

Gli analyzer statici faticano con reduce(). Nel processo di folding, il tipo dell'accumulatore può differire da quello degli elementi e l'inferenza dei tipi si complica. 

Considera questo codice.

from functools import reduce

def add_chars(acc, word):
    acc.extend(word)   # extend adds each character of the string
    return acc

chars = reduce(add_chars, ["hi", "ok"], [])
print(chars)  # ['h', 'i', 'o', 'k']

Anche se questo codice funziona, un analyzer statico potrebbe lamentare alcune cose.

  • Il tipo degli elementi di [] come initializer è ambiguo. 
  • Gli analyzer potrebbero assumere che i tipi di accumulatore ed elemento coincidano.

Per rendere il codice più chiaro a umani e analyzer, aggiungi type hint.

from functools import reduce
from typing import List

def add_chars(acc: List[str], word: str) -> List[str]:
    acc.extend(word)   # extend adds each character of the string
    return acc

chars: List[str] = reduce(add_chars, ["hi", "ok"], [])
print(chars)  # ['h', 'i', 'o', 'k']

Ora il reducer mostra esplicitamente

  • L'accumulatore è una List[str].
  • Ogni elemento è una str.
  • Il tipo di ritorno è una list[str].

Con questi hint, gli analyzer statici possono verificare il codice in modo rigoroso. 

Conclusione

Reduce proviene dalla programmazione funzionale, dove pliare collezioni in un unico risultato è un'idea centrale. Anche se non è più uno strumento di prima classe in Python, ha il suo posto. Quando ti servono pipeline flessibili, fold personalizzati o operazioni che non si mappano bene su funzioni esistenti, reduce() è uno strumento potente. Usato con attenzione, si integra bene con il più ampio ecosistema Python e rimane pratico nelle situazioni giuste.

Alcuni link correlati su Python che potresti trovare utili.

Python reduce() Domande Frequenti

Cosa fa reduce()?

Applica ripetutamente una funzione a due argomenti su un iterabile per ridurlo a un unico risultato.

Dove è definita reduce()?

Prima della versione 3, era una funzione built-in. Da allora si trova nel modulo functools.

Che tipo di funzioni dovrei passare a reduce()?

Funzioni semplici, associative e ben documentate. Evita effetti collaterali e logica non associativa.

Come si confronta con itertools.accumulate()?

reduce() restituisce solo il risultato finale, accumulate() produce tutti i risultati intermedi.

Quando dovrei usare un initializer?

Usa un initializer quando l'iterabile potrebbe essere vuoto.

Argomenti
Python
Data Science

Top corsi Python

Corso

Scrivere codice Python efficiente

4 ore
156K
Impara a scrivere codice efficiente che si esegua velocemente e usi le risorse in modo intelligente per evitare sprechi inutili.
Vedi i dettagliRight Arrow
Inizia Il Corso
Mostra altroRight Arrow
Correlato

blog

Tokenizzazione nel NLP: come funziona, sfide e casi d'uso

Guida al preprocessing NLP nel machine learning. Copriamo spaCy, i transformer di Hugging Face e come funziona la tokenizzazione in casi d'uso reali.
Abid Ali Awan's photo

Abid Ali Awan

10 min

blog

Che cos'è Snowflake? Guida per principianti alla piattaforma dati cloud

Esplora le basi di Snowflake, la piattaforma dati cloud. Scopri la sua architettura, le sue funzionalità e come integrarla nelle tue pipeline di dati.
Tim Lu's photo

Tim Lu

12 min

blog

I 15 migliori server MCP remoti che ogni AI builder dovrebbe conoscere nel 2026

Scopri i 15 migliori server MCP remoti che stanno trasformando lo sviluppo AI nel 2026. Scopri come migliorano automazione, ragionamento, sicurezza e velocità dei workflow.
Abid Ali Awan's photo

Abid Ali Awan

15 min

Mostra AltroMostra Altro