W3docs

Raggruppare Dati con le Tuple Python

Impara a raggruppare dati con le tuple Python: chiavi dict, raggruppamento multi-chiave, itertools.groupby, namedtuple, Counter e zip.

Le tuple sono ideali per raggruppare dati in Python. Poiché una tupla è immutabile e hashable, può fungere da chiave di dizionario — cosa che una lista non può mai fare. Questo rende le tuple la scelta naturale ogni volta che è necessario raggruppare record in base a una combinazione di campi, tracciare coordinate multidimensionali o contare eventi composti.

Questo capitolo tratta quattro pattern pratici di raggruppamento:

  • Tupla come chiave di dizionario — raggruppamento per campo singolo e multi-campo
  • itertools.groupby con le tuple — raggruppamento in streaming su sequenze ordinate
  • collections.namedtuple — aggiungere nomi ai record raggruppati
  • collections.Counter con le tuple — contare eventi composti

Capitoli correlati: Python Tuples · Access Tuples · Loop Tuples · Python Dictionaries · Python Lists Group

Perché le Tuple Possono Essere Chiavi di Dizionario

Python richiede che le chiavi di dizionario siano hashable — il loro valore non deve mai cambiare dopo che la chiave è stata memorizzata. Le tuple soddisfano questo requisito perché sono immutabili. Le liste non lo soddisfano e generano un TypeError quando si tenta di usarle come chiavi.

# A tuple can be a dictionary key
coordinates = {}
coordinates[(10, 20)] = "warehouse A"
coordinates[(30, 40)] = "warehouse B"
print(coordinates[(10, 20)])  # warehouse A

# A list cannot be a dictionary key
try:
    d = {[10, 20]: "warehouse A"}
except TypeError as e:
    print(f"TypeError: {e}")
# TypeError: unhashable type: 'list'

Un'importante insidia da tenere a mente: una tupla che contiene un elemento mutabile (come una lista) è anch'essa non hashable e non può essere usata come chiave:

try:
    d = {(1, [2, 3]): "value"}
except TypeError as e:
    print(f"TypeError: {e}")
# TypeError: unhashable type: 'list'

Assicurati che le chiavi tuple siano composte interamente da valori immutabili — stringhe, numeri, boolean o altre tuple.

Raggruppamento per un Singolo Campo di Tupla

Il caso d'uso più semplice consiste nel destrutturare una sequenza di tuple e raggruppare per un elemento. Usa collections.defaultdict(list) per evitare il codice ridondante necessario per verificare se una chiave esiste già.

from collections import defaultdict

employees = [
    ("Alice", "Engineering"),
    ("Bob", "Marketing"),
    ("Carol", "Engineering"),
    ("Dave", "Marketing"),
    ("Eve", "Engineering"),
]

by_dept = defaultdict(list)
for name, dept in employees:
    by_dept[dept].append(name)

for dept, members in sorted(by_dept.items()):
    print(f"{dept}: {members}")
# Engineering: ['Alice', 'Carol', 'Eve']
# Marketing: ['Bob', 'Dave']

defaultdict(list) crea automaticamente una lista vuota la prima volta che viene incontrata una nuova chiave dept, quindi non è necessario nessun controllo del tipo if dept not in by_dept.

Raggruppamento Multi-Chiave con una Chiave Tupla

Il vero potere delle chiavi tuple emerge quando è necessario raggruppare per più di un campo contemporaneamente. Combina i campi in una tupla e utilizza quella tupla come chiave del dizionario.

from collections import defaultdict

records = [
    ("Alice", "Engineering", "Senior"),
    ("Bob", "Marketing", "Junior"),
    ("Carol", "Engineering", "Junior"),
    ("Dave", "Marketing", "Senior"),
    ("Eve", "Engineering", "Senior"),
]

# Group by (department, level) — a two-field composite key
grouped = defaultdict(list)
for name, dept, level in records:
    grouped[(dept, level)].append(name)

for (dept, level), names in sorted(grouped.items()):
    print(f"{dept} / {level}: {names}")
# Engineering / Junior: ['Carol']
# Engineering / Senior: ['Alice', 'Eve']
# Marketing / Junior: ['Bob']
# Marketing / Senior: ['Dave']

Poiché (dept, level) è essa stessa una tupla, è hashable e può fungere da chiave dict indipendentemente da quanti campi contenga. Destrutturare la chiave con for (dept, level), names in ... mantiene il codice leggibile.

Raggruppamento di griglie e coordinate

Il raggruppamento multi-chiave con tuple gestisce naturalmente anche i dati spaziali:

points = [(0, 0), (1, 2), (0, 1), (1, 3), (2, 4)]

from collections import defaultdict

by_x = defaultdict(list)
for x, y in points:
    by_x[x].append(y)

for x, ys in sorted(by_x.items()):
    print(f"x={x}: y-values={ys}")
# x=0: y-values=[0, 1]
# x=1: y-values=[2, 3]
# x=2: y-values=[4]

Raggruppamento con itertools.groupby

itertools.groupby raggruppa elementi consecutivi che condividono la stessa chiave. È efficiente in termini di memoria perché è lazy — non carica tutti i gruppi in memoria contemporaneamente. Il compromesso è che l'input deve essere ordinato per la stessa chiave prima di essere passato a groupby, altrimenti si ottengono più gruppi parziali per la stessa chiave invece di uno solo.

from itertools import groupby

sales = [
    ("East", "Q1", 1200),
    ("East", "Q2", 1500),
    ("West", "Q1", 900),
    ("West", "Q2", 1100),
    ("East", "Q3", 1800),
]

# Sort by region (index 0) before grouping
sales_sorted = sorted(sales, key=lambda t: t[0])

for region, group in groupby(sales_sorted, key=lambda t: t[0]):
    items = list(group)
    total = sum(q[2] for q in items)
    print(f"{region}: total={total}, quarters={[q[1] for q in items]}")
# East: total=4500, quarters=['Q1', 'Q2', 'Q3']
# West: total=2000, quarters=['Q1', 'Q2']

Due cose da ricordare quando si usa groupby con le tuple:

  1. Ordina prima. Senza ordinamento, ogni nuova sequenza dello stesso valore chiave crea un gruppo separato.
  2. Consuma l'iteratore del gruppo immediatamente. L'iteratore interno group viene esaurito quando il ciclo esterno passa alla chiave successiva. Chiama sempre list(group) nel corpo del ciclo prima di usarlo altrove.

Quando groupby è preferibile a defaultdict

Usa groupby quando elabori una sequenza lunga già ordinata e non vuoi caricare l'intero risultato raggruppato in memoria. Per il raggruppamento generico senza garanzia di ordinamento, defaultdict(list) è più semplice e affidabile.

Raggruppamento con collections.namedtuple

namedtuple ti permette di assegnare nomi ai campi delle tuple, rendendo i dati raggruppati auto-documentanti. Una volta definito il tipo namedtuple, le istanze si comportano esattamente come le tuple ordinarie — sono immutabili, hashable e iterabili — ma i campi sono accessibili per nome oltre che per indice.

from collections import namedtuple, defaultdict

Employee = namedtuple("Employee", ["name", "department", "salary"])

employees = [
    Employee("Alice", "Engineering", 95000),
    Employee("Bob", "Marketing", 72000),
    Employee("Carol", "Engineering", 88000),
    Employee("Dave", "Marketing", 68000),
    Employee("Eve", "Engineering", 102000),
]

by_dept = defaultdict(list)
for emp in employees:
    by_dept[emp.department].append(emp)

for dept, members in sorted(by_dept.items()):
    avg_salary = sum(e.salary for e in members) / len(members)
    print(f"{dept}: {[e.name for e in members]}, avg salary={avg_salary:.0f}")
# Engineering: ['Alice', 'Carol', 'Eve'], avg salary=95000
# Marketing: ['Bob', 'Dave'], avg salary=70000

Nota che emp.department e emp.salary sono più leggibili di emp[1] e emp[2]. L'approccio con namedtuple è particolarmente utile quando la tupla ha molti campi e l'indicizzazione posizionale diventa difficile da seguire.

Contare Eventi Composti con Counter

collections.Counter conta oggetti hashable. Quando l'"oggetto" che vuoi contare è una combinazione di valori, racchiudi quei valori in una tupla e passa la sequenza di tuple a Counter.

from collections import Counter

log = [
    ("GET", 200),
    ("POST", 201),
    ("GET", 200),
    ("GET", 404),
    ("POST", 500),
    ("GET", 200),
    ("DELETE", 204),
]

counts = Counter(log)
for entry, n in counts.most_common():
    method, status = entry
    print(f"{method} {status}: {n} times")
# GET 200: 3 times
# POST 201: 1 times
# GET 404: 1 times
# POST 500: 1 times
# DELETE 204: 1 times

Counter utilizza la tupla come chiave hash internamente, quindi ogni combinazione unica (method, status) viene tracciata separatamente senza alcun codice di raggruppamento manuale.

Costruire Gruppi di Tuple con zip

zip accoppia elementi di due o più sequenze in tuple. Questo è un modo naturale per assemblare record raggruppati da liste parallele prima di applicare un'operazione di raggruppamento.

from collections import defaultdict

names = ["Alice", "Bob", "Carol"]
scores = [95, 87, 92]
departments = ["Engineering", "Marketing", "Engineering"]

# Pair the three sequences into tuples
records = list(zip(names, scores, departments))
print(records)
# [('Alice', 95, 'Engineering'), ('Bob', 87, 'Marketing'), ('Carol', 92, 'Engineering')]

# Now group by department
by_dept = defaultdict(list)
for name, score, dept in records:
    by_dept[dept].append((name, score))

for dept, members in sorted(by_dept.items()):
    print(f"{dept}: {members}")
# Engineering: [('Alice', 95), ('Carol', 92)]
# Marketing: [('Bob', 87)]

Scegliere lo Strumento di Raggruppamento Giusto

ObiettivoStrumento migliore
Raggruppare per un campo da una lista di tupledefaultdict(list)
Raggruppare per due o più campi contemporaneamentedefaultdict(list) con chiave tupla
Raggruppamento in streaming su una sequenza pre-ordinataitertools.groupby
Aggiungere nomi ai campi dei record raggruppaticollections.namedtuple
Contare le occorrenze di eventi composticollections.Counter
Assemblare liste parallele in tuple raggruppatezip

Errori Comuni

Dimenticare di ordinare prima di groupby. itertools.groupby unisce solo chiavi identiche consecutive. Se la stessa chiave appare in più posizioni non consecutive, ogni sequenza diventa un gruppo separato. Ordina sempre per la stessa funzione chiave prima di chiamare groupby.

Usare un valore mutabile all'interno di una chiave tupla. Una tupla contenente una lista non è hashable e genera un TypeError quando viene usata come chiave dict. Mantieni le chiavi tuple composte da stringhe, numeri, boolean o tuple annidate.

Consumare l'iteratore di groupby più di una volta. L'iteratore del sotto-gruppo di groupby viene esaurito non appena il ciclo esterno avanza. Chiama list(group) nel corpo del ciclo se hai bisogno di iterare il gruppo più di una volta.

Trattare l'output di defaultdict come un dict ordinario. Un defaultdict crea automaticamente nuove chiavi quando leggi una chiave mancante, il che può popolare silenziosamente il dict con liste vuote. Se devi verificare l'esistenza di una chiave senza creare nuove voci, converti prima in un dict ordinario: dict(grouped).

Argomenti Correlati

Esercitazione

Pratica
Which of the following can be used as a Python dictionary key?
Which of the following can be used as a Python dictionary key?
Was this page helpful?