Diccionarios con valores por defecto



Python provee varios métodos para retornar valores por defecto cuando no encontramos una clave determinada en un diccionario. Consideremos el siguiente código:

>>> d = {"a": 1}

Hemos creado un diccionario con una única clave "a", a la que le corresponde el valor 1. Tratar de obtener un elemento inexistente lanza una excepción.

>>> d["b"]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'b'

Pero supongamos que queremos que "b" retorne un valor por defecto si no se encuentra en el diccionario. En ese caso habría dos formas “manuales” de hacerlo. La primera es chequear si el elemento está dentro del diccionario.

>>> if "b" in d:
...     b = d["b"]
... else:
...     b = 0  # Valor por defecto.

El segundo método consiste en simplemente tratar de acceder al elemento y manejar la potencial excepción.

>>> try:
...     b = d["b"]
... except KeyError:
...     b = 0  # Valor por defecto.

Ambas formas son válidas, pero esta última es la recomendada. Tiene que ver con que en Python se considera “es mejor pedir perdón que pedir permiso” (easier to ask for forgiveness than permission) en contrapoisición a “mira antes de saltar” (look before you leap).

Se trata de dos paradigmas de programación representados por los dos códigos anteriores. El primero chequea que d contenga "b", mientras que el segundo directamente trata de acceder al elemento sin conocer a priori si éste existe. En Python se prefiere este último pues soporta el manejo de excepciones. En cambio, en lenguajes como C, se opta por el primero.

Volviendo a nuestro ejemplo, otra forma más concisa es vía el método get:

>>> b = d.get("b", 0)
>>> b
0

La función obtiene el nombre de una clave como primer argumento y, en segundo lugar, un valor por defecto en caso de no encontrarse.

Ahora bien, puede darse el caso en el que necesitemos que si se intenta acceder a un elemento inexistente, éste se agregue automáticamente al diccionario con un valor por defecto. Por suerte la librería estándar ya resolvió este problema con la implementación de collections.defaultdict.

defaultdict opera como un diccionario convencional (de hecho es una clase que hereda de dict, por lo que todas operaciones de un diccionario se aplican también a dicha clase). Al momento de crearlo debemos especificar una función que será llamada cuando se intente obtener una clave inexistente y su resultado será el valor de la nueva clave.

Por ejemplo, a continuación creamos un diccionario similar al anterior.

>>> from collections import defaultdict
>>> d = defaultdict(lambda: 0, {"a": 1})

El primer argumento indica una función (por eso usamos lambda) cuyo resultado será utilizado como valor por defecto. El segundo es un diccionario convencional.

>>> d = defaultdict(lambda: 0, {"a": 1})
>>> d["a"]
1
>>> d["b"]
0

El elemento "b" no existía, pero al intentar acceder a él fue automáticamente creado con el valor por defecto (0).

>>> list(d.items())
[('b', 0), ('a', 1)]

Nótese que esto ocurre únicamente al tratar de obtener una clave, y no se aplica a otras operaciones como, por ejemplo, chequear si una clave existe.

>>> "c" in d
False
>>> list(d.items())
[('a', 1), ('b', 0)]

Otros ejemplos:

>>> d = defaultdict(str)  # Una cadena vacía como valor por defecto.
>>> d["a"]
''
>>> d = defaultdict(list)  # Una lista vacía.
>>> d["a"]
[]
>>> def default_value():
...     return (1, 2, 3)
...
>>> d = defaultdict(default_value)
>>> d["a"]
(1, 2, 3)



Deja un comentario