Una de las colecciones más importantes en Python, si no es que la más importante, sin duda alguna son los diccionarios.
Este colección nos permite almacenar, y trabajar con, diferentes objetos usando el concepto clave-valor. Almacenaremos claves las cuales poseerán algún tipo de valor ( o None).
Aquí un pequeño ejemplo:
my_dict = {
'user1': [1, 2, 3],
'user2': [10, 20, 30]
}
En este caso tenemos un diccionario con 2 llaves de tipo string que almacenan valore de tipo listas.
Para poder obtener los valores, o simplemente actualizarlos, haremos uso de las llaves.
>>> my_dict['user1']
[1, 2, 3]
>>> my_dict['user2'].append(40)
[10, 20, 30, 40]
Todo esto quizás ya lo sepas, pero ¿Sabias que es possible definir valores por default para cada una de nuestras llaves? 🤔 Sí, así como lo lees, para esto haremos uso de Defaultdict.
En Python Defaultdict nos permite definir valores por default para las llaves en nuestros diccionarios, esto a su vez nos permite reducir, considerablemente, errores al momento de crear, leer o actualizar alguna llave. Todo esto sin mencionar que estaremos estandarizando nuestro objeto. Es por ello que, en esta ocasión me gustaría hablemos acerca de los Defaultdict, un tema que si duda te será de mucha utilizad en tu día a día como dev Python.
Bien, sin más introducción, comencemos con esta nueva entrega.
Defaultdict
Comencemos con un caso practico. Tenemos el siguiente diccionario al cual queremos incrementar en 1 el valor de la llave ‘a’.
my_dict = {
'a': 0
}
Para lograr esto podemos hacer algo como lo siguiente:
>>> my_dict['a'] += 1
>>> my_dict['a']
1
Esto funciona, pero ¿qué pasa si ahora intentamos incrementar en 1 el valor de la llave ‘b’? Si aplicamos el approach anterior obtendremos el siguiente error:
>>> my_dict['b'] += 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'b'
Esto se debe ya que la llave ‘b’ no existe en el diccionario al momento de intentar acceder y modificar su valor.
Para evitar este error, con un diccionario tradicional, podemos hacer algo como lo siguiente. Primero condicionamos si la llave no existe en el diccionario; en tal caso la agregamos y después realizamos la operación. Un approach mucho más defensivo.
if not 'b' in my_dict:
my_dict['b'] = 0
>>> my_dict['b'] += 1
>>> my_dict['b']
1
Ahora, si queremos evitar esta condición y asegurarnos que todas las nuevas llaves de nuestro diccionario posean un mismo valor por default de un mismo tipo de dato, haremos uso de Defaultdict.
Utilizando Defaultdict el código puede quedar de la siguiente manera.
from collections import defaultdict
my_dict = defaultdict(int)
>>> my_dict['a'] += 1
1
>>> my_dict['b'] += 1
1
>>> my_dict['c']
0
Para este nuevo ejemplo utilizamos la función defaultdict la cual nos retornar un objeto de la sub-clase diccionario. Este nuevo objeto tiene una particularidad muy interesante, y es que si intentemos acceder a una llave que no exista en dicho objeto, entonces esta se procede a crear y se asigna un valor por default. Para este ejemplo el valor por default es 0.
La función defaultdict recibe como argumento un factory, es decir, una función. Esta función será llamada cada vez que se añada una nueva llave al diccionario. La función, obligatoriamente, debe retornar el valor que deseamos nuestras llaves posean por default.
Recordemos que las funciones en Python son ciudadanos de primer clase. Así que pueden ser utilizadas como argumento para otras funciones.
Para este primer ejemplo de defaultdict estamos haciendo uso de la función int(), función que por default retorna 0.
Por supuesto, podemos utilizar funciones que Python ya nos ofrece como argumentos o podemos implementar nuestras propias funciones como factories. Aquí un par de ejemplos
from collections import defaultdict
# Utilzamos la función list
users_courses = defaultdict(list)
users_courses['user1'].append('Python')
users_courses['user2'].append('Django')
>>> users_courses
defaultdict(<class 'list'>, {'user1': ['Python'], 'user2': ['Django']})
def pywombat_function():
return 'PyWombat content'
courses = defaultdict(pywombat_function)
>>> courses['python']
'PyWombat content'
users_scores = defaultdict(lambda: 5)
>>> users_score['user1']
5
settings = {
'host': 'localhost',
'port': 80
}
user_settings = defaultdict(settings.copy)
user_settings['user1']['host'] = '127.0.0.1'
user_settings['user1']['post'] = 8000
El funcionamiento de un objeto defaultdict
es bastante sencillo: si intentamos acceder o modificar una llave que no existe, esta se crea automáticamente. Al crearse dicha llave, se llama a la función (factory) que hemos pasado como argumento al momento de crear el objeto. Esto permite definir un valor por defecto para cada una de las nuevas llaves. 🤠