defaultdict: evita KeyError en Python

Actualizado el: 20/08/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

    Documento y bandeja de entrada que representan buzones de correo con mailbox en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    mailbox en Python: buzones de correo

    Aprende mailbox en Python para leer, crear y migrar Maildir, mbox y MH con locking, flags, mensajes y manejo seguro

    Ler mais

    Tempo de leitura: 5 minutos
    12/08/2026
    Editor de texto que representa formato con textwrap en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    textwrap en Python: formatea textos

    Aprende textwrap en Python para dividir, rellenar, acortar, indentar y quitar sangrías con control de ancho, espacios y palabras largas.

    Ler mais

    Tempo de leitura: 5 minutos
    10/08/2026
    Carpeta y lupa que representan filtros de nombres con fnmatch en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    fnmatch en Python: filtra nombres de archivos

    Aprende fnmatch en Python para filtrar nombres de archivos con comodines, controlar mayúsculas, excluir patrones y distinguir glob de regex.

    Ler mais

    Tempo de leitura: 5 minutos
    10/08/2026
    Monitor con datos binarios que representa arrays numéricos compactos en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    array en Python: números compactos

    Aprende array en Python para almacenar números compactos, usar archivos binarios, byte order, memoryview y buffers seguros.

    Ler mais

    Tempo de leitura: 5 minutos
    10/08/2026
    Círculo cromático que representa conversiones RGB, HSV y HLS con colorsys en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    colorsys en Python: RGB, HSV y HLS

    Aprende colorsys en Python para convertir colores entre RGB, HSV, HLS y YIQ, crear paletas y evitar errores de escala

    Ler mais

    Tempo de leitura: 5 minutos
    09/08/2026
    Icono de configuración que representa archivos plist con plistlib en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    plistlib: lee y escribe archivos plist

    Aprende plistlib en Python para leer y escribir archivos plist XML y binarios, validar datos y manejar fechas, bytes y

    Ler mais

    Tempo de leitura: 6 minutos
    08/08/2026