El módulo collections amplía las estructuras integradas de Python con contenedores especializados. Sus clases permiten contar elementos, crear valores predeterminados, trabajar con colas eficientes, representar registros ligeros y combinar varios diccionarios. Muchas soluciones que requieren lógica manual con listas y diccionarios se vuelven más claras utilizando la herramienta adecuada.
Antes de elegir una colección especializada, conviene dominar las operaciones con diccionarios y los métodos de listas en Python. collections no reemplaza esas estructuras: las complementa.
Contar elementos con Counter
Counter es un diccionario especializado cuyos valores representan frecuencias:
from collections import Counter
palabras = ["python", "api", "python", "datos", "api", "python"]
conteo = Counter(palabras)
print(conteo)
print(conteo["python"])Una clave ausente devuelve cero en lugar de lanzar KeyError. La guía completa de Counter en Python muestra operaciones adicionales.
Elementos más comunes
for palabra, cantidad in conteo.most_common(2):
print(palabra, cantidad)most_common() es útil para análisis de texto, inventarios, registros y resultados de encuestas. Si todos los datos no caben en memoria, procesa por lotes y actualiza el contador.
Actualizar y restar contadores
ventas = Counter({"libro": 5, "curso": 2})
ventas.update(["libro", "curso", "curso"])
print(ventas)
devoluciones = Counter({"libro": 1})
ventas.subtract(devoluciones)
print(ventas)subtract() puede producir valores negativos. Si deseas conservar únicamente cantidades positivas, utiliza el operador unario +ventas para crear un contador filtrado.
Valores predeterminados con defaultdict
defaultdict llama a una función cuando una clave no existe:
from collections import defaultdict
grupos = defaultdict(list)
datos = [
("España", "Ana"),
("México", "Luis"),
("España", "Marta"),
]
for pais, nombre in datos:
grupos[pais].append(nombre)
print(grupos["España"])Sin defaultdict, tendrías que comprobar cada clave o utilizar setdefault(). La guía para evitar KeyError con defaultdict compara esas opciones.
La función default_factory
El primer argumento debe ser una función sin parámetros:
totales = defaultdict(int)
totales["visitas"] += 1
configuracion = defaultdict(lambda: "pendiente")
print(configuracion["estado"])No escribas defaultdict(list()), porque estarías pasando el resultado de una llamada en lugar de la función list.
Colas eficientes con deque
deque significa double-ended queue. Añadir o retirar elementos de ambos extremos es eficiente:
from collections import deque
cola = deque(["tarea-1", "tarea-2"])
cola.append("tarea-3")
actual = cola.popleft()
print(actual)
print(cola)Eliminar el primer elemento de una lista con pop(0) desplaza los demás y puede ser costoso. Para colas, deque.popleft() es la elección natural.
Deque con tamaño máximo
ultimos_eventos = deque(maxlen=3)
for evento in ["A", "B", "C", "D"]:
ultimos_eventos.append(evento)
print(ultimos_eventos)Cuando se alcanza maxlen, el elemento más antiguo se elimina automáticamente. Esto resulta útil para historiales, ventanas móviles y buffers.
Rotar elementos
turnos = deque(["Ana", "Luis", "Marta"])
turnos.rotate(-1)
print(turnos)rotate() desplaza elementos en círculo. Un valor positivo rota a la derecha y uno negativo a la izquierda.
Registros ligeros con namedtuple
namedtuple crea subclases de tupla cuyos campos tienen nombres:
from collections import namedtuple
Punto = namedtuple("Punto", ["x", "y"])
p = Punto(10, 20)
print(p.x)
print(p[1])Es inmutable, ligera y compatible con operaciones de tupla. Para modelos con valores predeterminados, métodos y validación, una dataclass suele ser más expresiva.
Modificar una namedtuple
Como es inmutable, _replace() devuelve una nueva instancia:
nuevo = p._replace(x=15)
print(nuevo)_asdict() convierte los campos en un diccionario, lo que facilita serialización y presentación.
Combinar configuraciones con ChainMap
ChainMap agrupa varios diccionarios sin copiarlos:
from collections import ChainMap
argumentos = {"modo": "rapido"}
entorno = {"region": "eu"}
predeterminados = {"modo": "seguro", "region": "global"}
config = ChainMap(argumentos, entorno, predeterminados)
print(config["modo"])
print(config["region"])La búsqueda ocurre de izquierda a derecha. Las escrituras se realizan en el primer diccionario, salvo que el código gestione otro comportamiento. Es útil para capas de configuración, pero debes documentar claramente la precedencia.
UserDict, UserList y UserString
Estas clases envuelven los tipos integrados para facilitar personalizaciones:
from collections import UserDict
class DiccionarioNormalizado(UserDict):
def __setitem__(self, clave, valor):
super().__setitem__(str(clave).lower(), valor)
datos = DiccionarioNormalizado()
datos["NOMBRE"] = "Ana"
print(datos["nombre"])En muchas situaciones puedes heredar directamente de dict, pero las clases de usuario ofrecen un modelo de extensión explícito y consistente.
Elegir la estructura correcta
Usa Counter para frecuencias, defaultdict para agrupaciones y acumuladores, deque para colas y buffers, namedtuple para registros inmutables sencillos y ChainMap para capas de configuración. Si solo necesitas almacenar pares clave-valor, un diccionario normal sigue siendo la opción más clara.
Combinar con itertools
Las colecciones especializadas se integran bien con iteradores. La guía de itertools en Python muestra agrupaciones, combinaciones y procesamiento perezoso. Por ejemplo, puedes alimentar un Counter con datos producidos por un generador sin construir listas intermedias innecesarias.
Rendimiento y memoria
No elijas una estructura solo por una supuesta optimización. Primero considera la operación dominante. deque mejora inserciones en extremos, pero no está pensada para acceso aleatorio intenso. Counter añade semántica y métodos, pero sigue almacenando claves y cantidades en memoria.
La documentación oficial de collections contiene todas las APIs y ejemplos. El tutorial oficial de estructuras de datos de Python ayuda a comparar listas, conjuntos y diccionarios.
Errores frecuentes
Entre los errores habituales están utilizar pop(0) para una cola grande, compartir un mismo valor mutable como predeterminado de forma incorrecta, asumir que Counter elimina valores negativos, modificar una namedtuple como si fuera mutable y olvidar la precedencia de ChainMap. Escribe pruebas para los casos ausentes y límites de tamaño.
Conclusión
collections convierte patrones comunes en estructuras con intención clara. Un Counter comunica que estás contando, una deque expresa una cola y un defaultdict muestra que las claves nacen con un valor. Esa claridad suele ser tan valiosa como el rendimiento. Domina primero las estructuras integradas y adopta cada contenedor especializado cuando simplifique realmente el código.






