defaultdict en Python: evita KeyError y simplifica diccionarios

Publicado el: 11/07/2026
Tempo de leitura: 4 minutos
Como evitar KeyError usando defaultdict em Python

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.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Compactando arquivos ZIP automaticamente com Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    Cómo crear archivos ZIP con Python y zipfile

    Crea archivos ZIP con Python y zipfile: carpetas, filtros, arcname, compresión, contenido en memoria, verificación, hashes y backups seguros.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Redimensionamento de imagens com Pillow em Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    Cómo redimensionar imágenes con Pillow en Python

    Redimensiona imágenes con Pillow en Python conservando proporciones, creando miniaturas, corrigiendo EXIF, procesando carpetas y exportando WebP.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Copiando e movendo arquivos com Python usando shutil
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    Cómo copiar y mover archivos en Python con shutil

    Aprende shutil en Python para copiar, mover, eliminar y comprimir archivos y carpetas con validaciones, hashes, logs y modo simulación.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Monitoramento de pastas em tempo real com Python Watchdog
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    Watchdog en Python: monitoriza carpetas en tiempo real

    Aprende Watchdog en Python para monitorizar carpetas, filtrar eventos, evitar duplicados, esperar archivos completos y automatizar procesos seguros.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Instalação offline de pacotes Python sem internet
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    Cómo instalar paquetes de Python sin conexión a Internet

    Aprende a instalar paquetes de Python sin Internet con pip download, ruedas, requirements.txt, --no-index, hashes y repositorios internos.

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026
    Como acelerar código Python usando lru cache
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    lru_cache en Python: acelera funciones con caché

    Aprende lru_cache en Python: memoización, maxsize, cache_info, cache_clear, argumentos hashables, recursión y errores frecuentes.

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026