defaultdict del módulo collections es un diccionario que crea automáticamente el valor por defecto cuando se accede a una clave que no existe, eliminando el patrón if key not in d: d[key] = valor_inicial que aparece continuamente al acumular datos en diccionarios.
El problema que resuelve
# Sin defaultdict: necesitas comprobar la clave antes de usarla
grupos = {}
for nombre, departamento in empleados:
if departamento not in grupos:
grupos[departamento] = []
grupos[departamento].append(nombre)
# Con defaultdict(list): el [] se crea automáticamente
from collections import defaultdict
grupos = defaultdict(list)
for nombre, departamento in empleados:
grupos[departamento].append(nombre) # limpio y directo
defaultdict(list): agrupar elementos
from collections import defaultdict
ventas = [
("Ana", "Enero", 1200),
("Luis", "Enero", 800),
("Ana", "Febrero", 1500),
("María", "Enero", 950),
("Luis", "Febrero", 700),
]
por_vendedor = defaultdict(list)
for vendedor, mes, importe in ventas:
por_vendedor[vendedor].append((mes, importe))
for vendedor, registros in por_vendedor.items():
total = sum(r[1] for r in registros)
print(f"{vendedor}: {total} ({len(registros)} ventas)")
defaultdict(int): contar sin Counter
from collections import defaultdict
texto = "mississippi"
frecuencias = defaultdict(int)
for c in texto:
frecuencias[c] += 1 # int() devuelve 0, así que c=0+1=1 la primera vez
print(dict(sorted(frecuencias.items())))
# {'i': 4, 'm': 1, 'p': 2, 's': 4}
# Para esto Counter es más cómodo, pero defaultdict(int) es útil
# cuando acumulas cálculos más complejos que un simple +1
defaultdict(set): colecciones sin duplicados
from collections import defaultdict
# Tags únicos por artículo
articulos = [
("python", "tutorial"),
("python", "avanzado"),
("javascript", "tutorial"),
("python", "tutorial"), # duplicado, se ignora
]
tags_por_lenguaje = defaultdict(set)
for lenguaje, tag in articulos:
tags_por_lenguaje[lenguaje].add(tag)
for lenguaje, tags in tags_por_lenguaje.items():
print(f"{lenguaje}: {sorted(tags)}")
# python: ['avanzado', 'tutorial']
# javascript: ['tutorial']
Diferencias con setdefault()
from collections import defaultdict
# setdefault: el valor por defecto se evalúa en CADA llamada
grupos = {}
grupos.setdefault("ventas", []).append("Ana")
grupos.setdefault("ventas", []).append("Luis")
print(grupos) # {'ventas': ['Ana', 'Luis']}
# defaultdict: el callable se llama SOLO cuando la clave no existe
grupos = defaultdict(list)
grupos["ventas"].append("Ana")
grupos["ventas"].append("Luis")
print(grupos) # defaultdict(, {'ventas': ['Ana', 'Luis']})
# Diferencia importante: acceder a una clave en defaultdict la CREA
d = defaultdict(int)
_ = d["nueva_clave"] # crea la clave con valor 0
print("nueva_clave" in d) # True
# Con dict normal y get(), la clave NO se crea
d2 = {}
_ = d2.get("nueva_clave", 0)
print("nueva_clave" in d2) # False
defaultdict anidado
from collections import defaultdict
# Contador bidimensional
def nuevo_defaultdict_int():
return defaultdict(int)
matriz = defaultdict(nuevo_defaultdict_int)
# o con lambda:
matriz = defaultdict(lambda: defaultdict(int))
matriz["España"]["Madrid"] += 1
matriz["España"]["Barcelona"] += 3
matriz["Francia"]["París"] += 2
print(dict(matriz["España"])) # {'Madrid': 1, 'Barcelona': 3}
Usa defaultdict cuando la operación por defecto sobre una clave nueva sea siempre la misma (una lista vacía, un contador en 0, un set vacío). Para el caso de solo contar elementos, Counter es más expresivo; para todo lo demás, defaultdict es la herramienta correcta.
