defaultdict en Python: evitar KeyError con valores por defecto automáticos

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.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP