Python defaultdict: Simplifica las operaciones de diccionario con valores predeterminados
Actualizado el
Todo desarrollador de Python ha chocado con este muro: escribes un bucle limpio para agrupar o contar elementos usando un diccionario, ejecutas el código y un KeyError hace que todo el script se detenga porque una clave aún no existía. La solución habitual es añadir verificaciones if key in dict o bloques try/except KeyError por todas partes. Tu lógica para agrupar diez líneas de datos de repente se infla a veinte líneas de código defensivo repetitivo.
Esto empeora a escala. Cuando estás construyendo listas de adyacencia para grafos, agregando datos de registro o contando frecuencias de palabras en millones de registros, esas cláusulas de protección se acumulan. Te ralentizan como desarrollador, hacen el código más difícil de revisar e introducen errores sutiles cuando olvidas una verificación en una rama.
El collections.defaultdict de Python elimina toda esta categoría de problemas. Es una subclase de diccionario que llama a una función factoría para proporcionar valores faltantes automáticamente. Sin más KeyError, sin más cláusulas de protección, sin más código repetitivo.
- Runcell Science: alternativa open source a Claude Science para investigación
- Cómo evitar que Mac se duerma: mantener Codex, Claude Code y agentes de IA corriendo
- OpenClaw vs ZeroClaw vs Pi Agent vs Nanobot: ¿Qué stack de agentes de IA deberías elegir en 2026?
- Cómo Claude Code analiza notebooks de Jupyter en Data Science: lo que sí hace y lo que no
- Claude Code routines: qué son y por qué importan los cron jobs de agentes de IA
- Claude Code Desktop Bypass Permissions: cómo activarlo
- Cómo Construir Dos Agentes en Python con el Protocolo A2A de Google - Tutorial Paso a Paso
- Las 10 principales bibliotecas de visualización de datos en Python que crecen en 2025
¿Qué es defaultdict?
El defaultdict es una subclase del dict incorporado de Python. La diferencia clave: cuando accedes a una clave que no existe, defaultdict la crea automáticamente con un valor predeterminado en lugar de lanzar un KeyError.
from collections import defaultdict
# Un dict regular lanza KeyError
regular = {}
# regular['missing'] # KeyError: 'missing'
# defaultdict crea el valor automáticamente
dd = defaultdict(int)
dd['missing'] # Retorna 0, y ahora 'missing' es una clave
print(dd) # defaultdict(<class 'int'>, {'missing': 0})El constructor toma una función factoría como primer argumento. Factorías comunes:
int-- retorna0list-- retorna[]set-- retornaset()str-- retorna""lambda: value-- retorna cualquier valor predeterminado personalizado
defaultdict(int) -- El patrón de conteo
El uso más común. Cada nueva clave comienza en 0, así que puedes incrementar inmediatamente.
from collections import defaultdict
words = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']
# Sin defaultdict
counts_regular = {}
for word in words:
if word in counts_regular:
counts_regular[word] += 1
else:
counts_regular[word] = 1
# Con defaultdict(int) -- limpio y directo
counts = defaultdict(int)
for word in words:
counts[word] += 1
print(dict(counts))
# {'apple': 3, 'banana': 2, 'cherry': 1}defaultdict(list) -- El patrón de agrupación
Agrupa elementos relacionados. Cada nueva clave comienza con una lista vacía.
from collections import defaultdict
students = [
('Math', 'Alice'),
('Science', 'Bob'),
('Math', 'Charlie'),
('Science', 'Diana'),
('Math', 'Eve'),
('History', 'Frank'),
]
groups = defaultdict(list)
for subject, student in students:
groups[subject].append(student)
for subject, names in groups.items():
print(f"{subject}: {', '.join(names)}")
# Math: Alice, Charlie, Eve
# Science: Bob, Diana
# History: FrankAgrupar registros por múltiples campos
from collections import defaultdict
sales = [
{'region': 'East', 'product': 'Widget', 'amount': 100},
{'region': 'West', 'product': 'Gadget', 'amount': 200},
{'region': 'East', 'product': 'Widget', 'amount': 150},
{'region': 'West', 'product': 'Widget', 'amount': 300},
]
by_region_product = defaultdict(list)
for sale in sales:
key = (sale['region'], sale['product'])
by_region_product[key].append(sale['amount'])
for (region, product), amounts in by_region_product.items():
total = sum(amounts)
print(f"{region} - {product}: {amounts} (total: {total})")defaultdict(set) -- Agrupación única
Recopila valores únicos por clave automáticamente.
from collections import defaultdict
edges = [
('Alice', 'Bob'), ('Alice', 'Charlie'),
('Bob', 'Alice'), ('Bob', 'Diana'),
('Alice', 'Bob'), # duplicate
]
connections = defaultdict(set)
for person, friend in edges:
connections[person].add(friend)
for person, friends in connections.items():
print(f"{person} is connected to: {friends}")
# Alice is connected to: {'Bob', 'Charlie'}
# Bob is connected to: {'Alice', 'Diana'}defaultdict(lambda: value) -- Valores predeterminados personalizados
Cuando los tipos integrados no se ajustan, usa un lambda para retornar cualquier valor predeterminado.
from collections import defaultdict
# Valor predeterminado 'N/A' para entradas faltantes
status = defaultdict(lambda: 'N/A')
status['server1'] = 'running'
status['server2'] = 'stopped'
print(status['server3']) # N/A
# Saldo inicial predeterminado
accounts = defaultdict(lambda: 100.0)
accounts['alice'] += 50
accounts['bob'] -= 30
print(dict(accounts)) # {'alice': 150.0, 'bob': 70.0}Diccionario predeterminado con valores estructurados
from collections import defaultdict
def default_profile():
return {'score': 0, 'level': 1, 'items': []}
profiles = defaultdict(default_profile)
profiles['player1']['score'] += 100
profiles['player1']['items'].append('sword')
profiles['player2']['level'] = 5
print(profiles['player1'])
# {'score': 100, 'level': 1, 'items': ['sword']}
print(profiles['player3'])
# {'score': 0, 'level': 1, 'items': []}defaultdict anidado -- Estructuras de árbol
Uno de los patrones más potentes es usar defaultdict recursivamente para crear diccionarios auto-vivificantes.
from collections import defaultdict
def tree():
return defaultdict(tree)
taxonomy = tree()
taxonomy['Animal']['Mammal']['Dog'] = 'Canis lupus familiaris'
taxonomy['Animal']['Mammal']['Cat'] = 'Felis catus'
taxonomy['Animal']['Bird']['Eagle'] = 'Aquila chrysaetos'
taxonomy['Plant']['Tree']['Oak'] = 'Quercus'
print(taxonomy['Animal']['Mammal']['Dog']) # Canis lupus familiarisAgregación multinivel
from collections import defaultdict
sales_data = [
(2025, 'Q1', 'Widget', 500),
(2025, 'Q1', 'Gadget', 300),
(2025, 'Q2', 'Widget', 700),
(2026, 'Q1', 'Widget', 600),
]
report = defaultdict(lambda: defaultdict(lambda: defaultdict(int)))
for year, quarter, product, amount in sales_data:
report[year][quarter][product] += amount
print(report[2025]['Q1']['Widget']) # 500
print(report[2026]['Q1']['Widget']) # 600defaultdict vs dict.setdefault() vs get() -- Comparación
| Característica | defaultdict | dict.setdefault() | dict.get() |
|---|---|---|---|
| Requiere import | Sí (collections) | No | No |
| Crea clave automáticamente | Sí | Sí | No |
| Modifica dict al acceder | Sí | Sí | No |
| Valor personalizado por llamada | No (factoría global) | Sí | Sí |
| Rendimiento (repetido) | Más rápido | Más lento (overhead de llamada al método) | Más rápido (sin mutación) |
| Mejor para | Acumulación repetida | Valores predeterminados puntuales | Lectura con respaldo |
Cuándo usar cada uno:
defaultdict: construir valores a lo largo de muchas iteraciones (contar, agrupar)dict.setdefault(): necesitar ocasionalmente un valor predeterminado para una clave específicadict.get(): leer un valor con respaldo sin modificar el diccionario
Convertir defaultdict de vuelta a dict regular
from collections import defaultdict
import json
def defaultdict_to_dict(d):
"""Recursively convert defaultdict to regular dict."""
if isinstance(d, defaultdict):
d = {k: defaultdict_to_dict(v) for k, v in d.items()}
return d
nested = defaultdict(lambda: defaultdict(int))
nested['x']['y'] = 10
nested['a']['b'] = 20
regular = defaultdict_to_dict(nested)
print(json.dumps(regular)) # {"x": {"y": 10}, "a": {"b": 20}}También puedes desactivar la factoría predeterminada estableciéndola en None:
dd = defaultdict(int)
dd['a'] += 1
dd.default_factory = None
# dd['missing'] # Ahora lanza KeyErrorEjemplos prácticos
Lista de adyacencia para grafos
from collections import defaultdict, deque
edges = [('A', 'B'), ('A', 'C'), ('B', 'D'), ('C', 'D'), ('D', 'E')]
graph = defaultdict(list)
for src, dst in edges:
graph[src].append(dst)
graph[dst].append(src) # undirected graph
def bfs(graph, start):
visited = set()
queue = deque([start])
order = []
while queue:
node = queue.popleft()
if node not in visited:
visited.add(node)
order.append(node)
queue.extend(graph[node])
return order
print(bfs(graph, 'A')) # ['A', 'B', 'C', 'D', 'E']Índice invertido para búsqueda de texto
from collections import defaultdict
documents = {
'doc1': 'python is a great programming language',
'doc2': 'data science uses python extensively',
'doc3': 'machine learning with python and data',
}
index = defaultdict(set)
for doc_id, text in documents.items():
for word in text.split():
index[word.lower()].add(doc_id)
def search(query):
return index.get(query.lower(), set())
print(search('python')) # {'doc1', 'doc2', 'doc3'}
print(search('data')) # {'doc2', 'doc3'}Visualizar datos agrupados con PyGWalker
Después de agrupar y agregar datos con defaultdict, a menudo querrás visualizar los resultados. PyGWalker (opens in a new tab) convierte tu DataFrame de pandas en una interfaz de visualización interactiva directamente en Jupyter:
from collections import defaultdict
import pandas as pd
import pygwalker as pyg
sales = [
('Electronics', 'Laptop', 1200),
('Electronics', 'Phone', 800),
('Clothing', 'Shirt', 45),
('Clothing', 'Jacket', 120),
]
totals = defaultdict(lambda: defaultdict(int))
for category, product, amount in sales:
totals[category][product] += amount
rows = []
for category, products in totals.items():
for product, total in products.items():
rows.append({'category': category, 'product': product, 'total': total})
df = pd.DataFrame(rows)
walker = pyg.walk(df)FAQ
¿Qué es defaultdict en Python?
defaultdict es una subclase de diccionario en collections que proporciona un valor predeterminado para claves faltantes. En lugar de lanzar un KeyError, llama a una función factoría (como int, list o set) para crear y almacenar un valor predeterminado automáticamente.
¿Cuál es la diferencia entre dict y defaultdict?
La única diferencia funcional es cómo manejan las claves faltantes. Un dict regular lanza KeyError. Un defaultdict llama a su función default_factory para crear un valor predeterminado. En todos los demás aspectos se comportan de manera idéntica.
¿Cuándo debo usar defaultdict(list) vs defaultdict(set)?
Usa defaultdict(list) cuando quieras agrupar elementos y preservar duplicados y el orden de inserción. Usa defaultdict(set) cuando quieras recopilar solo elementos únicos por clave.
¿Puedo serializar un defaultdict a JSON?
Sí, pero para objetos defaultdict anidados, conviértelos primero a dict regular usando una función de conversión recursiva. También puedes establecer default_factory = None para evitar la creación accidental de claves antes de la serialización.
¿Cómo creo un defaultdict anidado?
Define una función factoría recursiva: def tree(): return defaultdict(tree). Para anidación de dos niveles más simple, usa defaultdict(lambda: defaultdict(int)).
Conclusión
El collections.defaultdict de Python es una de las herramientas más prácticas en la biblioteca estándar. Convierte patrones de acumulación de diccionarios verbosos y propensos a errores en líneas únicas limpias. Usa defaultdict(int) para contar, defaultdict(list) para agrupar, defaultdict(set) para recopilación única y defaultdict anidado para datos jerárquicos.
La conclusión clave: si te encuentras escribiendo if key not in dict antes de cada operación de diccionario, reemplaza ese diccionario por un defaultdict. Tu código será más corto, más rápido y mucho más fácil de mantener.