W3docs

Módulo collections de Python

Aprende el módulo collections de Python: Counter, defaultdict, namedtuple, deque, OrderedDict y ChainMap, con ejemplos prácticos y cuándo usar cada uno.

El módulo collections incorporado de Python proporciona tipos de contenedor especializados que extienden o reemplazan la lista, el dict y la tupla estándar. Cada tipo resuelve un problema específico que, de otro modo, requeriría varias líneas adicionales de gestión manual.

Este capítulo cubre los seis tipos de uso más común: Counter, defaultdict, namedtuple, deque, OrderedDict y ChainMap. Para cada uno verás qué problema resuelve, cómo crearlo y usarlo, y los errores típicos que hay que tener en cuenta.

No se necesita instalación — collections viene incluido con cada instalación de Python 3:

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

Counter

Counter es una subclase de dict diseñada para contar objetos hashables. Le pasas un iterable (o una cadena, o argumentos con nombre) y devuelve un objeto similar a un diccionario donde las claves son los elementos y los valores son sus recuentos.

Creación de 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})

Las claves que no existen devuelven 0 en lugar de lanzar un KeyError:

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

Elementos más frecuentes

most_common(n) devuelve los n elementos con mayor recuento como una lista de tuplas (elemento, recuento), ordenados de mayor a menor frecuencia:

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

Omite n para obtener todos los elementos ordenados por frecuencia.

Aritmética de Counter

Los Counters admiten suma, resta, intersección y unión:

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

Cuándo usar Counter: recuento de votos, frecuencias de palabras, recuentos de caracteres, construcción de histogramas.

Trampa del Counter: la resta conserva solo los positivos

a - b descarta silenciosamente los elementos donde el resultado sería cero o negativo. Si necesitas conservar recuentos negativos, usa subtract() en su lugar:

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

defaultdict

defaultdict es una subclase de dict que llama a una función de fábrica para proporcionar un valor por defecto cada vez que accedes a una clave que aún no existe. Esto elimina la necesidad de cláusulas de guarda del tipo if key not in d:.

Creación de un defaultdict

Pasa la fábrica como primer argumento:

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}

Sin defaultdict necesitarías dd[word] = dd.get(word, 0) + 1 o un Counter.

Agrupación de elementos con list como fábrica

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']}

Funciones de fábrica comunes

FábricaValor por defectoUso típico
int0Conteo
float0.0Acumulación de sumas
list[]Agrupación de elementos
setset()Recolección de valores únicos
str''Construcción de cadenas
dict{}Mapeos anidados

También puedes pasar una lambda sin argumentos para un valor por defecto personalizado: defaultdict(lambda: 'N/A').

Trampa del defaultdict: acceder a una clave la crea

A diferencia de dict.get(), un simple acceso dd[key] sobre una clave inexistente inserta esa clave con el valor por defecto. Esto puede sorprenderte al iterar o comprobar la pertenencia:

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 cuando quieras comprobar sin efectos secundarios.

Cuándo usar defaultdict: agrupación de datos, construcción de listas de adyacencia para grafos, cualquier patrón donde inicializas y luego actualizas.


namedtuple

namedtuple crea una nueva clase cuyas instancias son como tuplas normales pero con campos con nombre. El resultado es inmutable, eficiente en memoria (sin __dict__ por instancia) y autodocumentado.

Creación de 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

El primer argumento de namedtuple() es el nombre del tipo (usado en repr). El segundo argumento es una lista de nombres de campo (o una cadena separada por espacios o comas: 'x y').

Ejemplo práctico

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

El acceso por nombre (emp.name) es mucho más claro que el acceso posicional (row[0]) al leer datos de archivos CSV o filas de base de datos.

Métodos útiles de 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 introdujo dataclasses.dataclass como alternativa. Elige namedtuple cuando quieras inmutabilidad y compatibilidad total con tuplas (desempaquetado, indexación, hash). Elige dataclass cuando necesites campos mutables, fábricas de valores por defecto o métodos.

Cuándo usar namedtuple: representación de registros (filas de base de datos, líneas CSV, pares de coordenadas, colores RGB) donde importan la inmutabilidad y el bajo consumo de memoria.


deque

deque (cola de doble extremo, pronunciada "dec") es una secuencia optimizada para incorporaciones y extracciones O(1) desde ambos extremos. Una lista normal logra O(1) en append y O(n) en insert(0, …); deque logra O(1) en ambos extremos.

Creación de un deque

from collections import deque

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

Incorporar y extraer elementos

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])

Rotación de un deque

rotate(n) desplaza los elementos hacia la derecha n posiciones (negativo = hacia la izquierda):

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

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

deque acotado (ventana deslizante / búfer FIFO)

Establecer maxlen limita el tamaño del deque. Cuando se añaden nuevos elementos más allá del límite, los elementos caen automáticamente por el extremo opuesto — perfecto para conservar los últimos N eventos:

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

Trampa del deque: acceso aleatorio lento O(n)

deque no admite acceso aleatorio eficiente. d[500] es O(n), no O(1) como en una lista. Si indexas frecuentemente por posición, usa una lista. Usa deque solo cuando necesites incorporaciones y extracciones rápidas en ambos extremos.

Cuándo usar deque: implementación de colas y pilas, algoritmos de ventana deslizante, búsqueda en anchura, almacenamiento de las últimas N entradas de registro.


OrderedDict

Desde Python 3.7, el dict normal mantiene el orden de inserción. ¿Por qué usar entonces OrderedDict?

Dos razones siguen siendo relevantes:

  1. move_to_end() — permite reordenar eficientemente las claves hacia el frente o el final.
  2. Igualdad — dos instancias de OrderedDict con las mismas claves pero diferente orden de inserción se comparan como no iguales, a diferencia de los dicts normales.

Creación y reordenación de 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']

Igualdad sensible al orden

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

Cuándo usar OrderedDict: implementaciones de caché LRU (mover la clave usada recientemente al final), cualquier algoritmo donde el orden de inserción debe formar parte de la igualdad.


ChainMap

ChainMap agrupa múltiples diccionarios en una única vista lógica. Las búsquedas recorren los mapas en orden; las escrituras y eliminaciones afectan únicamente al primer mapa.

Uso básico

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

Las escrituras van solo al primer mapa:

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

Simulación de ámbitos de variables 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() devuelve un nuevo ChainMap con un dict vacío nuevo antepuesto, que es como Python modela internamente sus propias reglas de ámbito (local → envolvente → global → incorporado).

Cuándo usar ChainMap: capas de configuración (sobreescrituras del usuario → valores por defecto del proyecto → valores por defecto globales), implementación de entornos con ámbito, combinación de argumentos de CLI con variables de entorno y archivos de configuración.


Elegir el tipo adecuado

Necesitas…Usa
Contar ocurrencias de elementosCounter
Evitar KeyError con un valor por defectodefaultdict
Representar un registro con campos con nombrenamedtuple
Incorporaciones/extracciones rápidas en ambos extremos, o un búfer acotadodeque
Igualdad de dict sensible al orden, o move_to_end()OrderedDict
Combinar múltiples dicts en una vista sin copiarChainMap

Para más información sobre los tipos base que extienden, consulta Python Dictionaries, Python Lists y Python Tuples. Para asistentes basados en iteradores de la biblioteca estándar, consulta el Módulo itertools de Python.

Práctica

Práctica
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?
Práctica
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?
Práctica
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?