defaultdict es una estructura del módulo collections que crea automáticamente un valor predeterminado cuando accedes a una clave inexistente. Esto evita muchos bloques repetitivos de comprobación y reduce errores KeyError en contadores, agrupaciones, índices y estructuras anidadas.
Para ampliar conceptos, consulta las guías sobre diccionarios, módulo collections, Counter, funciones y excepciones. Como referencias externas, revisa la documentación oficial de defaultdict y la referencia oficial de dict.
El problema de KeyError
Un diccionario normal lanza KeyError si intentas leer una clave que no existe:
totales = {}
print(totales["python"])
Para evitarlo, puedes comprobar la clave:
if "python" not in totales:
totales["python"] = 0
totales["python"] += 1
Este patrón funciona, pero se repite con frecuencia. defaultdict lo simplifica.
Crear un defaultdict
from collections import defaultdict
totales = defaultdict(int)
totales["python"] += 1
print(totales["python"])
La función int actúa como fábrica predeterminada. Cuando falta una clave, Python llama a int(), que devuelve cero.
Qué es default_factory
El primer argumento de defaultdict se guarda como default_factory. Debe ser una función o callable sin argumentos:
datos = defaultdict(list)
print(datos.default_factory)
Al acceder a una clave nueva, la fábrica crea el valor, lo inserta en el diccionario y lo devuelve.
Contar elementos
palabras = ["python", "web", "python", "datos"]
conteo = defaultdict(int)
for palabra in palabras:
conteo[palabra] += 1
print(dict(conteo))
Para conteos simples, Counter puede ser todavía más directo. defaultdict(int) es útil cuando el conteo forma parte de una estructura mayor o requiere lógica personalizada.
Agrupar valores
registros = [
("Ana", "Python"),
("Luis", "JavaScript"),
("Marta", "Python"),
]
por_curso = defaultdict(list)
for nombre, curso in registros:
por_curso[curso].append(nombre)
Cada clave recibe una lista independiente. No necesitas crearla manualmente.
Evitar el error de una lista compartida
La fábrica debe ser list, no una lista ya creada:
# Correcto
agrupados = defaultdict(list)
No se pasa [] porque no es callable. Además, queremos una lista nueva para cada clave, no el mismo objeto compartido.
Usar set para evitar duplicados
permisos = defaultdict(set)
permisos["ana"].add("editar")
permisos["ana"].add("editar")
permisos["ana"].add("publicar")
El set elimina permisos repetidos y conserva una estructura clara por usuario.
Estructuras anidadas
inventario = defaultdict(lambda: defaultdict(int))
inventario["almacen-1"]["teclado"] += 3
inventario["almacen-1"]["raton"] += 2
inventario["almacen-2"]["teclado"] += 1
Este patrón funciona, pero puede volverse difícil de leer cuando hay muchos niveles. En proyectos grandes, considera clases, dataclasses o funciones auxiliares.
Valores predeterminados personalizados
def estado_inicial():
return {
"intentos": 0,
"activo": True,
"mensajes": [],
}
usuarios = defaultdict(estado_inicial)
usuarios["ana"]["intentos"] += 1
La función se ejecuta cada vez que aparece una clave nueva, por lo que cada usuario obtiene su propio diccionario y su propia lista.
Acceso que modifica el diccionario
Una diferencia importante es que leer una clave inexistente con corchetes la inserta:
datos = defaultdict(list)
print("x" in datos) # False
print(datos["x"]) # []
print("x" in datos) # True
Esto puede sorprender. Si solo quieres consultar sin crear, usa get():
valor = datos.get("y")
get() no activa la fábrica.
defaultdict frente a dict.get()
get() es adecuado para una consulta puntual:
cantidad = totales.get("python", 0)
Pero este código no almacena automáticamente el valor. Para acumular, necesitas reasignar:
totales["python"] = totales.get("python", 0) + 1
defaultdict resulta más claro cuando el mismo patrón se repite muchas veces.
defaultdict frente a setdefault()
grupos = {}
grupos.setdefault("python", []).append("Ana")
setdefault() funciona con diccionarios normales, pero puede ser menos legible y construir el valor predeterminado incluso cuando la clave ya existe. Para agrupaciones repetidas, defaultdict(list) suele comunicar mejor la intención.
defaultdict frente a Counter
Counter está especializado en frecuencias y ofrece métodos como most_common(). Usa Counter para conteos directos y defaultdict para acumulaciones o estructuras más generales.
Convertir a dict normal
resultado = dict(por_curso)
La conversión elimina la fábrica, pero los valores internos permanecen iguales. En una estructura anidada, la conversión superficial no transforma automáticamente los defaultdict interiores.
Serialización JSON
El módulo json suele serializar un defaultdict como un diccionario porque hereda de dict. Aun así, conviene convertir explícitamente para dejar clara la salida:
import json
texto = json.dumps(dict(conteo), ensure_ascii=False)
Para estructuras anidadas, crea una función recursiva de conversión.
Copias
Una copia superficial mantiene referencias a listas o diccionarios internos. Si necesitas independencia completa, utiliza copy.deepcopy(). Comprueba también que la fábrica sea apropiada para el nuevo objeto.
Type hints
from collections import defaultdict
usuarios_por_curso: defaultdict[str, list[str]] = defaultdict(list)
Las anotaciones ayudan a editores y analizadores estáticos, pero no cambian el comportamiento en ejecución.
Ejemplo práctico: índice de palabras
from collections import defaultdict
def crear_indice(lineas):
indice = defaultdict(list)
for numero, linea in enumerate(lineas, start=1):
for palabra in linea.lower().split():
limpia = palabra.strip(".,;:!?()[]")
if limpia:
indice[limpia].append(numero)
return indice
El resultado relaciona cada palabra con las líneas donde aparece.
Ejemplo práctico: totales por categoría
from decimal import Decimal
from collections import defaultdict
movimientos = [
("comida", Decimal("12.50")),
("transporte", Decimal("4.20")),
("comida", Decimal("8.30")),
]
totales = defaultdict(Decimal)
for categoria, valor in movimientos:
totales[categoria] += valor
Decimal() sin argumentos crea cero, por lo que funciona como fábrica.
Cuándo no usarlo
No uses defaultdict si una clave ausente representa un error que debe detectarse. En configuraciones obligatorias, por ejemplo, devolver un valor silencioso puede ocultar un problema. Un KeyError puede ser la señal correcta.
Errores frecuentes
Los fallos comunes son pasar un resultado en lugar de una función, olvidar que el acceso inserta claves, crear demasiados niveles anidados, usar valores mutables compartidos y aplicar un predeterminado cuando la ausencia debería ser excepcional.
Pruebas recomendadas
Comprueba claves nuevas, claves existentes, independencia de listas, comportamiento de get(), conversión a dict y serialización. También verifica que la fábrica se ejecute únicamente cuando corresponde.
Conclusión
defaultdict elimina código repetitivo en conteos, agrupaciones y estructuras por clave. Elige una fábrica que cree un valor nuevo y coherente, recuerda que los corchetes insertan claves ausentes y utiliza un diccionario normal cuando la ausencia deba producir un error. Bien aplicado, hace que la intención del programa sea más clara y reduce fallos de inicialización.






