W3docs

Modulo collections di Python

Scopri il modulo collections di Python: Counter, defaultdict, namedtuple, deque, OrderedDict e ChainMap con esempi pratici e quando usarli.

Il modulo integrato collections di Python fornisce tipi di contenitore specializzati che estendono o sostituiscono le strutture standard list, dict e tuple. Ogni tipo risolve un problema specifico che altrimenti richiederebbe diverse righe di codice aggiuntivo.

Questo capitolo tratta tutti e sei i tipi più utilizzati: Counter, defaultdict, namedtuple, deque, OrderedDict e ChainMap. Per ognuno vedrai quale problema risolve, come crearlo e utilizzarlo, e le insidie da evitare.

Non è necessaria nessuna installazione — collections è incluso in ogni installazione di Python 3:

from collections import Counter, defaultdict, namedtuple, deque, OrderedDict, ChainMap

Counter

Counter è una sottoclasse di dict progettata per contare oggetti hashable. Gli si fornisce un iterabile (o una stringa, o argomenti keyword) e restituisce un oggetto simile a un dizionario in cui le chiavi sono gli elementi e i valori sono i rispettivi conteggi.

Creare un Counter

from collections import Counter

# From a list
word_list = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']
c = Counter(word_list)
print(c)
# Output: Counter({'apple': 3, 'banana': 2, 'cherry': 1})

Le chiavi mancanti restituiscono 0 invece di sollevare un KeyError:

print(c['apple'])   # 3
print(c['mango'])   # 0  — no KeyError

Elementi più frequenti

most_common(n) restituisce gli n elementi con il conteggio più alto come lista di tuple (elemento, conteggio), ordinate dalla più alla meno frequente:

print(c.most_common(2))
# Output: [('apple', 3), ('banana', 2)]

Ometti n per ottenere tutti gli elementi ordinati per frequenza.

Aritmetica con Counter

I Counter supportano addizione, sottrazione, intersezione e unione:

a = Counter(['a', 'a', 'b'])          # Counter({'a': 2, 'b': 1})
b = Counter(['a', 'b', 'b', 'c'])     # Counter({'b': 2, 'a': 1, 'c': 1})

print(a + b)   # Counter({'a': 3, 'b': 3, 'c': 1})
print(a - b)   # Counter({'a': 1})        — only positive counts kept
print(a & b)   # Counter({'a': 1, 'b': 1}) — minimum of each count
print(a | b)   # Counter({'a': 2, 'b': 2, 'c': 1}) — maximum of each count

Quando usare Counter: conteggio di voti, frequenze delle parole, conteggio di caratteri, costruzione di istogrammi.

Insidia di Counter: la sottrazione mantiene solo i valori positivi

a - b scarta silenziosamente gli elementi in cui il risultato sarebbe zero o negativo. Se hai bisogno di mantenere i conteggi negativi, usa invece subtract():

a = Counter({'x': 2})
b = Counter({'x': 5})
a.subtract(b)
print(a)   # Counter({'x': -3})  — negative count preserved

defaultdict

defaultdict è una sottoclasse di dict che chiama una funzione factory per fornire un valore predefinito ogni volta che si accede a una chiave che non esiste ancora. Questo elimina la necessità di clausole di guardia come if key not in d:.

Creare un defaultdict

Passa la factory come primo argomento:

from collections import defaultdict

dd = defaultdict(int)   # default value: int() == 0
words = ['cat', 'dog', 'cat', 'bird', 'dog', 'cat']
for word in words:
    dd[word] += 1       # no KeyError on first access

print(dict(dd))
# Output: {'cat': 3, 'dog': 2, 'bird': 1}

Senza defaultdict sarebbe necessario usare dd[word] = dd.get(word, 0) + 1 oppure un Counter.

Raggruppare elementi con list come factory

groups = defaultdict(list)
data = [('fruit', 'apple'), ('veggie', 'carrot'), ('fruit', 'banana'), ('veggie', 'broccoli')]
for category, item in data:
    groups[category].append(item)

print(dict(groups))
# Output: {'fruit': ['apple', 'banana'], 'veggie': ['carrot', 'broccoli']}

Funzioni factory più comuni

FactoryValore predefinitoUso tipico
int0Conteggio
float0.0Accumulo di somme
list[]Raggruppamento di elementi
setset()Raccolta di valori unici
str''Costruzione di stringhe
dict{}Mappature annidate

Puoi anche passare una lambda senza argomenti per un valore predefinito personalizzato: defaultdict(lambda: 'N/A').

Insidia di defaultdict: accedere a una chiave la crea

A differenza di dict.get(), un semplice accesso dd[key] su una chiave mancante inserisce quella chiave con il valore predefinito. Questo può sorprendere durante l'iterazione o il controllo dell'appartenenza:

dd = defaultdict(int)
print('foo' in dd)   # False — key does not exist yet
_ = dd['foo']        # access inserts the key
print('foo' in dd)   # True  — key was silently created

Usa dd.get('foo') o 'foo' in dd quando vuoi verificare senza effetti collaterali.

Quando usare defaultdict: raggruppamento di dati, costruzione di liste di adiacenza per grafi, qualsiasi schema in cui si inizializza e poi si aggiorna.


namedtuple

namedtuple crea una nuova classe le cui istanze sono come tuple normali ma con campi denominati. Il risultato è immutabile, efficiente in termini di memoria (nessun __dict__ per istanza) e autodescrittivo.

Creare un namedtuple

from collections import namedtuple

Point = namedtuple('Point', ['x', 'y'])
p = Point(3, 7)

print(p)         # Point(x=3, y=7)
print(p.x)       # 3
print(p.y)       # 7
print(p[0])      # 3  — index access still works

Il primo argomento di namedtuple() è il nome del tipo (usato nel repr). Il secondo argomento è un elenco di nomi di campi (o una stringa separata da spazi/virgole: 'x y').

Esempio pratico

Employee = namedtuple('Employee', ['name', 'department', 'salary'])
emp = Employee('Alice', 'Engineering', 95000)
print(emp.name, emp.department, emp.salary)
# Output: Alice Engineering 95000

L'accesso per nome (emp.name) è molto più chiaro dell'accesso per posizione (row[0]) quando si leggono dati da file CSV o righe di database.

Metodi utili di namedtuple

# Convert to an ordered dictionary
print(p._asdict())        # {'x': 3, 'y': 7}

# Create a modified copy (namedtuples are immutable)
p2 = p._replace(x=10)
print(p2)                 # Point(x=10, y=7)
print(p)                  # Point(x=3, y=7)  — original unchanged

namedtuple vs dataclass

Python 3.7 ha introdotto dataclasses.dataclass come alternativa. Scegli namedtuple quando vuoi immutabilità e piena compatibilità con le tuple (unpacking, indicizzazione, hashing). Scegli dataclass quando hai bisogno di campi mutabili, factory predefinite o metodi.

Quando usare namedtuple: rappresentare record (righe di database, righe CSV, coppie di coordinate, colori RGB) dove immutabilità e basso consumo di memoria sono importanti.


deque

deque (coda a doppia estremità, pronunciato "deck") è una sequenza ottimizzata per append e pop O(1) da entrambe le estremità. Una lista normale garantisce O(1) per append e O(n) per insert(0, …); deque garantisce O(1) da entrambe le estremità.

Creare un deque

from collections import deque

d = deque([1, 2, 3])
print(d)   # deque([1, 2, 3])

Aggiungere e rimuovere elementi

d.append(4)        # add to right
d.appendleft(0)    # add to left
print(d)           # deque([0, 1, 2, 3, 4])

d.pop()            # remove from right  → 4
d.popleft()        # remove from left   → 0
print(d)           # deque([1, 2, 3])

Rotazione di un deque

rotate(n) sposta gli elementi verso destra di n posizioni (negativo = sinistra):

d = deque([1, 2, 3])
d.rotate(1)
print(d)   # deque([3, 1, 2])

d.rotate(-1)
print(d)   # deque([1, 2, 3])

deque limitato (finestra scorrevole / buffer FIFO)

Impostare maxlen limita la dimensione del deque. Quando vengono aggiunti nuovi elementi oltre il limite, gli elementi cadono automaticamente dall'estremità opposta — perfetto per conservare gli ultimi N eventi:

buffer = deque(maxlen=3)
for i in range(5):
    buffer.append(i)
print(buffer)   # deque([2, 3, 4], maxlen=3)

Insidia di deque: accesso casuale lento O(n)

deque non supporta l'accesso casuale efficiente. d[500] è O(n), non O(1) come in una lista. Se indicizzi frequentemente per posizione, usa una lista. Usa deque solo quando hai bisogno di aggiunta e rimozione rapide da entrambe le estremità.

Quando usare deque: implementazione di code e stack, algoritmi a finestra scorrevole, ricerca in ampiezza (BFS), conservazione delle ultime N voci di log.


OrderedDict

Da Python 3.7, il dict normale mantiene l'ordine di inserimento. Allora perché usare OrderedDict?

Rimangono rilevanti due motivi:

  1. move_to_end() — consente di riordinare in modo efficiente le chiavi verso l'inizio o la fine.
  2. Uguaglianza — due istanze di OrderedDict con le stesse chiavi ma ordine di inserimento diverso sono considerate non uguali, a differenza dei dict normali.

Creare e riordinare un OrderedDict

from collections import OrderedDict

od = OrderedDict()
od['one'] = 1
od['two'] = 2
od['three'] = 3
print(list(od.keys()))   # ['one', 'two', 'three']

od.move_to_end('one')          # move 'one' to the end
print(list(od.keys()))         # ['two', 'three', 'one']

od.move_to_end('three', last=False)  # move 'three' to the front
print(list(od.keys()))               # ['three', 'two', 'one']

Uguaglianza sensibile all'ordine

od1 = OrderedDict([('a', 1), ('b', 2)])
od2 = OrderedDict([('b', 2), ('a', 1)])
print(od1 == od2)   # False — different order

d1 = {'a': 1, 'b': 2}
d2 = {'b': 2, 'a': 1}
print(d1 == d2)     # True  — regular dicts ignore order

Quando usare OrderedDict: implementazioni di cache LRU (sposta la chiave usata di recente alla fine), qualsiasi algoritmo in cui l'ordine di inserimento deve far parte dell'uguaglianza.


ChainMap

ChainMap raggruppa più dizionari in un'unica vista logica. Le ricerche esaminano le mappe nell'ordine; le scritture e le eliminazioni influenzano sempre solo la prima mappa.

Utilizzo di base

from collections import ChainMap

defaults = {'color': 'blue', 'size': 'medium', 'theme': 'light'}
overrides = {'color': 'red', 'size': 'large'}

combined = ChainMap(overrides, defaults)
print(combined['color'])   # 'red'   — found in overrides first
print(combined['theme'])   # 'light' — not in overrides, falls back to defaults

Le scritture vengono indirizzate solo alla prima mappa:

combined['font'] = 'serif'
print(overrides)   # {'color': 'red', 'size': 'large', 'font': 'serif'}
print(defaults)    # {'color': 'blue', 'size': 'medium', 'theme': 'light'} — unchanged

Simulare scope di variabili con new_child()

base = ChainMap({'x': 1})
child = base.new_child({'x': 99, 'y': 2})
print(child['x'])            # 99  — child scope shadows parent
print(child['y'])            # 2
print(child.parents['x'])    # 1   — access parent scope directly

new_child() restituisce un nuovo ChainMap con un dict vuoto preposto, il che è il modo in cui le regole di scoping di Python (locale → racchiudente → globale → built-in) sono modellate internamente.

Quando usare ChainMap: stratificazione della configurazione (override utente → impostazioni predefinite del progetto → impostazioni predefinite globali), implementazione di ambienti con scope, combinazione di argomenti CLI con variabili d'ambiente e file di configurazione.


Scegliere il tipo giusto

Hai bisogno di…Usa
Contare le occorrenze degli elementiCounter
Evitare KeyError con un valore predefinitodefaultdict
Rappresentare un record con campi denominatinamedtuple
Append/pop veloci da entrambe le estremità, o un buffer limitatodeque
Uguaglianza dict sensibile all'ordine, o move_to_end()OrderedDict
Unire più dict in un'unica vista senza copiareChainMap

Per ulteriori informazioni sui tipi base che questi estendono, consulta Python Dictionaries, Python Lists e Python Tuples. Per gli helper basati su iteratori della libreria standard, consulta il Modulo itertools di Python.

Pratica

Pratica
Which collections type returns 0 (instead of raising KeyError) when you access a missing key and count occurrences automatically?
Which collections type returns 0 (instead of raising KeyError) when you access a missing key and count occurrences automatically?
Pratica
A deque with maxlen=3 already holds [1, 2, 3]. What does it contain after append(4) is called?
A deque with maxlen=3 already holds [1, 2, 3]. What does it contain after append(4) is called?
Pratica
Which statement about OrderedDict is true in Python 3.7 and later?
Which statement about OrderedDict is true in Python 3.7 and later?
Was this page helpful?