Diccionarios¶
Podemos trasladar el concepto de diccionario de la vida real al de diccionario en Python. Al fin y al cabo un diccionario es un objeto que contiene palabras, y cada palabra tiene asociado un significado. Haciendo el paralelismo, diríamos que en Python un diccionario es también un objeto indexado por claves (las palabras) que tienen asociados unos valores (los significados). [1]
Los diccionarios en Python tienen las siguientes características:
Mantienen el orden en el que se insertan las claves. [2]
Son mutables, con lo que admiten añadir, borrar y modificar sus elementos.
Las claves deben ser únicas. A menudo se utilizan las cadenas de texto como claves, pero en realidad podría ser cualquier tipo de datos inmutable: enteros, flotantes, tuplas (entre otros).
Tienen un acceso muy rápido a sus elementos, debido a la forma en la que están implementados internamente. [3]
Nota
En otros lenguajes de programación, a los diccionarios se les conoce como arrays asociativos, «hashes» o «hashmaps».
Creando diccionarios¶
Para crear un diccionario usamos llaves {}
rodeando asignaciones clave: valor
que están separadas por comas. Veamos algunos ejemplos de diccionarios:
>>> empty_dict = {}
>>> rae = {
... 'bifronte': 'De dos frentes o dos caras',
... 'anarcoide': 'Que tiende al desorden',
... 'montuvio': 'Campesino de la costa'
... }
>>> population_can = {
... 2015: 2_135_209,
... 2016: 2_154_924,
... 2017: 2_177_048,
... 2018: 2_206_901,
... 2019: 2_220_270
... }
En el código anterior podemos observar la creación de un diccionario vacío, otro donde sus claves y sus valores son cadenas de texto y otro donde las claves y los valores son valores enteros.
Ejecución paso a paso a través de Python Tutor:
Advertencia
Aunque está permitido, NUNCA llames dict
a una variable porque destruirías la función que nos permite crear diccionarios. Y tampoco uses nombres derivados como _dict
o dict_
ya que no son nombres representativos que identifiquen el propósito de la variable.
Ejercicio
Entre en el intérprete interactivo de Python (>>>
) y cree un diccionario con los nombres (como claves) de 5 personas de su familia y sus edades (como valores).
Conversión¶
Para convertir otros tipos de datos en un diccionario podemos usar la función dict()
:
>>> # Diccionario a partir de una lista de cadenas de texto
>>> dict(['a1', 'b2'])
{'a': '1', 'b': '2'}
>>> # Diccionario a partir de una tupla de cadenas de texto
>>> dict(('a1', 'b2'))
{'a': '1', 'b': '2'}
>>> # Diccionario a partir de una lista de listas
>>> dict([['a', 1], ['b', 2]])
{'a': 1, 'b': 2}
Nota
Si nos fijamos bien, cualquier iterable que tenga una estructura interna de 2 elementos es susceptible de convertirse en un diccionario a través de la función dict()
.
Diccionario vacío¶
Existe una manera particular de usar dict()
y es no pasarle ningún argumento. En este caso estaremos queriendo convertir el «vacío» en un diccionario, con lo que obtendremos un diccionario vacío:
>>> dict()
{}
Truco
Para crear un diccionario vacío, se suele recomendar el uso de {}
frente a dict()
, no sólo por ser más pitónico sino por tener (en promedio) un mejor rendimiento en tiempos de ejecución.
Creación con dict()
¶
También es posible utilizar la función dict()
para crear dicionarios y no tener que utilizar llaves y comillas:
Supongamos que queremos transformar la siguiente tabla en un diccionario:
Atributo |
Valor |
---|---|
|
Guido |
|
Van Rossum |
|
Python creator |
Utilizando la construcción mediante dict
podemos pasar clave y valor como argumentos de la función:
>>> person = dict(
... name='Guido',
... surname='Van Rossum',
... job='Python creator'
... )
>>> person
{'name': 'Guido', 'surname': 'Van Rossum', 'job': 'Python creator'}
El inconveniente que tiene esta aproximación es que las claves deben ser identificadores válidos en Python. Por ejemplo, no se permiten espacios:
>>> person = dict(
... name='Guido van Rossum',
... date of birth='31/01/1956'
File "<stdin>", line 3
date of birth='31/01/1956'
^
SyntaxError: invalid syntax
Es posible crear un diccionario especificando sus claves y un único valor de «relleno»:
>>> dict.fromkeys('aeiou', 0)
{'a': 0, 'e': 0, 'i': 0, 'o': 0, 'u': 0}
Nota
Es válido pasar cualquier «iterable» como referencia a las claves.
Operaciones con diccionarios¶
Obtener un elemento¶
Para obtener un elemento de un diccionario basta con escribir la clave entre corchetes. Veamos un ejemplo:
>>> rae = {
... 'bifronte': 'De dos frentes o dos caras',
... 'anarcoide': 'Que tiende al desorden',
... 'montuvio': 'Campesino de la costa'
... }
>>> rae['anarcoide']
'Que tiende al desorden'
Si intentamos acceder a una clave que no existe, obtendremos un error:
>>> rae['acceso']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'acceso'
Usando get()
¶
Existe una función muy útil para «superar» los posibles errores de acceso por claves inexistentes. Se trata de get()
y su comportamiento es el siguiente:
Si la clave que buscamos existe, nos devuelve su valor.
Si la clave que buscamos no existe, nos devuelve
None
[4] salvo que le indiquemos otro valor por defecto, pero en ninguno de los dos casos obtendremos un error.
1>>> rae
2{'bifronte': 'De dos frentes o dos caras',
3 'anarcoide': 'Que tiende al desorden',
4 'montuvio': 'Campesino de la costa'}
5
6>>> rae.get('bifronte')
7'De dos frentes o dos caras'
8
9>>> rae.get('programación')
10
11>>> rae.get('programación', 'No disponible')
12'No disponible'
- Línea 6:
Equivalente a
rae['bifronte']
.- Línea 9:
La clave buscada no existe y obtenemos
None
. [5]- Línea 11:
La clave buscada no existe y nos devuelve el valor que hemos aportado por defecto.
Añadir o modificar un elemento¶
Para añadir un elemento a un diccionario sólo es necesario hacer referencia a la clave y asignarle un valor:
Si la clave ya existía en el diccionario, se reemplaza el valor existente por el nuevo.
Si la clave es nueva, se añade al diccionario con su valor. No vamos a obtener un error a diferencia de las listas.
Partimos del siguiente diccionario para ejemplificar estas acciones:
>>> rae = {
... 'bifronte': 'De dos frentes o dos caras',
... 'anarcoide': 'Que tiende al desorden',
... 'montuvio': 'Campesino de la costa'
... }
Vamos a añadir la palabra enjuiciar a nuestro diccionario de la Real Academia de La Lengua:
>>> rae['enjuiciar'] = 'Someter una cuestión a examen, discusión y juicio'
>>> rae
{'bifronte': 'De dos frentes o dos caras',
'anarcoide': 'Que tiende al desorden',
'montuvio': 'Campesino de la costa',
'enjuiciar': 'Someter una cuestión a examen, discusión y juicio'}
Supongamos ahora que queremos modificar el significado de la palabra enjuiciar por otra acepción:
>>> rae['enjuiciar'] = 'Instruir, juzgar o sentenciar una causa'
>>> rae
{'bifronte': 'De dos frentes o dos caras',
'anarcoide': 'Que tiende al desorden',
'montuvio': 'Campesino de la costa',
'enjuiciar': 'Instruir, juzgar o sentenciar una causa'}
Creando desde vacío¶
Una forma muy habitual de trabajar con diccionarios es utilizar el patrón creación partiendo de uno vacío e ir añadiendo elementos poco a poco.
Supongamos un ejemplo en el que queremos construir un diccionario donde las claves son las letras vocales y los valores son sus posiciones:
>>> VOWELS = 'aeiou'
>>> enum_vowels = {}
>>> for i, vowel in enumerate(VOWELS, start=1):
... enum_vowels[vowel] = i
...
>>> enum_vowels
{'a': 1, 'e': 2, 'i': 3, 'o': 4, 'u': 5}
Nota
Hemos utilizando la función enumerate()
que ya vimos para las listas en el apartado: Iterar usando enumeración.
Ejercicio
pypas: extract-cities
Pertenencia de una clave¶
La forma pitónica de comprobar la existencia de una clave dentro de un diccionario, es utilizar el operador in
:
>>> 'bifronte' in rae
True
>>> 'almohada' in rae
False
>>> 'montuvio' not in rae
False
Nota
El operador in
siempre devuelve un valor booleano, es decir, verdadero o falso.
Ejercicio
pypas: count-letters
Longitud de un diccionario¶
Podemos conocer el número de elementos («clave-valor») que tiene un diccionario con la función len()
:
>>> rae
{'bifronte': 'De dos frentes o dos caras',
'anarcoide': 'Que tiende al desorden',
'montuvio': 'Campesino de la costa',
'enjuiciar': 'Instruir, juzgar o sentenciar una causa'}
>>> len(rae)
4
Obtener todos los elementos¶
Python ofrece mecanismos para obtener todos los elementos de un diccionario. Partimos del siguiente diccionario:
>>> rae
{'bifronte': 'De dos frentes o dos caras',
'anarcoide': 'Que tiende al desorden',
'montuvio': 'Campesino de la costa',
'enjuiciar': 'Instruir, juzgar o sentenciar una causa'}
- Obtener todas las claves de un diccionario:
Mediante la función
keys()
:>>> rae.keys() dict_keys(['bifronte', 'anarcoide', 'montuvio', 'enjuiciar'])
- Obtener todos los valores de un diccionario:
Mediante la función
values()
:>>> rae.values() dict_values([ 'De dos frentes o dos caras', 'Que tiende al desorden', 'Campesino de la costa', 'Instruir, juzgar o sentenciar una causa' ])
- Obtener todos los pares «clave-valor» de un diccionario:
Mediante la función
items()
:>>> rae.items() dict_items([ ('bifronte', 'De dos frentes o dos caras'), ('anarcoide', 'Que tiende al desorden'), ('montuvio', 'Campesino de la costa'), ('enjuiciar', 'Instruir, juzgar o sentenciar una causa') ])
Nota
Para este último caso cabe destacar que los «items» se devuelven como una lista de tuplas, donde cada tupla tiene dos elementos: el primero representa la clave y el segundo representa el valor.
Iterar sobre un diccionario¶
En base a los elementos que podemos obtener, Python nos proporciona tres maneras de iterar sobre un diccionario.
Iterar sobre claves:
>>> for word in rae.keys():
... print(word)
...
bifronte
anarcoide
montuvio
enjuiciar
Iterar sobre valores:
>>> for meaning in rae.values():
... print(meaning)
...
De dos frentes o dos caras
Que tiende al desorden
Campesino de la costa
Instruir, juzgar o sentenciar una causa
Iterar sobre «clave-valor»:
>>> for word, meaning in rae.items():
... print(f'{word}: {meaning}')
...
bifronte: De dos frentes o dos caras
anarcoide: Que tiende al desorden
montuvio: Campesino de la costa
enjuiciar: Instruir, juzgar o sentenciar una causa
Nota
En este último caso, recuerde el uso de los «f-strings» para formatear cadenas de texto.
Ejercicio
pypas: avg-population
Borrar elementos¶
Python nos ofrece, al menos, tres formas para borrar elementos en un diccionario:
- Por su clave:
Mediante la sentencia
del
:>>> rae = { ... 'bifronte': 'De dos frentes o dos caras', ... 'anarcoide': 'Que tiende al desorden', ... 'montuvio': 'Campesino de la costa' ... } >>> del rae['bifronte'] >>> rae {'anarcoide': 'Que tiende al desorden', 'montuvio': 'Campesino de la costa'}
- Por su clave (con extracción):
Mediante la función
pop()
podemos extraer un elemento del diccionario por su clave. Vendría a ser una combinación deget()
+del
:>>> rae = { ... 'bifronte': 'De dos frentes o dos caras', ... 'anarcoide': 'Que tiende al desorden', ... 'montuvio': 'Campesino de la costa' ... } >>> rae.pop('anarcoide') 'Que tiende al desorden' >>> rae {'bifronte': 'De dos frentes o dos caras', 'montuvio': 'Campesino de la costa'} >>> rae.pop('bucle') Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'bucle'
Advertencia
Si la clave que pretendemos extraer con
pop()
no existe, obtendremos un error.- Borrado completo del diccionario:
Utilizando la función
clear()
:>>> rae = { ... 'bifronte': 'De dos frentes o dos caras', ... 'anarcoide': 'Que tiende al desorden', ... 'montuvio': 'Campesino de la costa' ... } >>> rae.clear() >>> rae {}
⏵ En este caso borramos el contenido de la variable.
«Reinicializando» el diccionario a vacío con
{}
:>>> rae = { ... 'bifronte': 'De dos frentes o dos caras', ... 'anarcoide': 'Que tiende al desorden', ... 'montuvio': 'Campesino de la costa' ... } >>> rae = {} >>> rae {}
⏵ En este caso creamos una nueva variable «vacía».
Ejercicio
pypas: merge-dicts
Combinar diccionarios¶
Dados dos (o más) diccionarios, es posible «mezclarlos» para obtener una combinación de los mismos. Esta combinación se basa en dos premisas:
Si la clave no existe, se añade con su valor.
Si la clave ya existe, se añade con el valor del «último» diccionario en la mezcla. [6]
Python ofrece dos mecanismos para realizar esta combinación. Vamos a partir de los siguientes diccionarios para ejemplificar su uso:
>>> rae1 = {
... 'bifronte': 'De dos frentes o dos caras',
... 'enjuiciar': 'Someter una cuestión a examen, discusión y juicio'
... }
>>> rae2 = {
... 'anarcoide': 'Que tiende al desorden',
... 'montuvio': 'Campesino de la costa',
... 'enjuiciar': 'Instruir, juzgar o sentenciar una causa'
... }
- Sin modificar los diccionarios originales:
Mediante el operador
**
:>>> {**rae1, **rae2} {'bifronte': 'De dos frentes o dos caras', 'enjuiciar': 'Instruir, juzgar o sentenciar una causa', 'anarcoide': 'Que tiende al desorden', 'montuvio': 'Campesino de la costa'}
A partir de Python 3.9 podemos utilizar el operador
|
para combinar dos diccionarios:>>> rae1 | rae2 {'bifronte': 'De dos frentes o dos caras', 'enjuiciar': 'Instruir, juzgar o sentenciar una causa', 'anarcoide': 'Que tiende al desorden', 'montuvio': 'Campesino de la costa'}
- Modificando los diccionarios originales:
Mediante la función
update()
:>>> rae1.update(rae2) >>> rae1 {'bifronte': 'De dos frentes o dos caras', 'enjuiciar': 'Instruir, juzgar o sentenciar una causa', 'anarcoide': 'Que tiende al desorden', 'montuvio': 'Campesino de la costa'}
Nota
Tener en cuenta que el orden en el que especificamos los diccionarios a la hora de su combinación (mezcla) es relevante en el resultado final. En este caso el orden de los factores sí altera el producto.
Cuidado con las copias¶
Al igual que ocurría con las listas, si hacemos un cambio en un diccionario, se verá reflejado en todas las variables que hagan referencia al mismo. Esto se deriva de su propiedad de ser mutable. Veamos un ejemplo concreto:
>>> original_rae = {
... 'bifronte': 'De dos frentes o dos caras',
... 'anarcoide': 'Que tiende al desorden',
... 'montuvio': 'Campesino de la costa'
... }
>>> copy_rae = original_rae
>>> original_rae['bifronte'] = 'bla bla bla'
>>> original_rae
{'bifronte': 'bla bla bla',
'anarcoide': 'Que tiende al desorden',
'montuvio': 'Campesino de la costa'}
>>> copy_rae
{'bifronte': 'bla bla bla',
'anarcoide': 'Que tiende al desorden',
'montuvio': 'Campesino de la costa'}
Una posible solución a este problema es hacer una «copia dura». Para ello Python proporciona la función copy()
:
>>> original_rae = {
... 'bifronte': 'De dos frentes o dos caras',
... 'anarcoide': 'Que tiende al desorden',
... 'montuvio': 'Campesino de la costa'
... }
>>> copy_rae = original_rae.copy()
>>> original_rae['bifronte'] = 'bla bla bla'
>>> original_rae
{'bifronte': 'bla bla bla',
'anarcoide': 'Que tiende al desorden',
'montuvio': 'Campesino de la costa'}
>>> copy_rae
{'bifronte': 'De dos frentes o dos caras',
'anarcoide': 'Que tiende al desorden',
'montuvio': 'Campesino de la costa'}
Truco
En el caso de que estemos trabajando con diccionarios que contienen elementos mutables, debemos hacer uso de la función deepcopy()
dentro del módulo copy
de la librería estándar.
Diccionarios por comprensión¶
De forma análoga a cómo se escriben las listas por comprensión, podemos aplicar este método a los diccionarios usando llaves {
}
.
Veamos un ejemplo en el que creamos un diccionario por comprensión donde las claves son palabras y los valores son sus longitudes:
>>> words = ('sun', 'space', 'rocket', 'earth')
>>> words_length = {word: len(word) for word in words}
>>> words_length
{'sun': 3, 'space': 5, 'rocket': 6, 'earth': 5}
También podemos aplicar condiciones a estas comprensiones. Continuando con el ejemplo anterior, podemos incorporar la restricción de sólo incluir palabras que no empiecen por vocal:
>>> words = ('sun', 'space', 'rocket', 'earth')
>>> words_length = {w: len(w) for w in words if w[0] not in 'aeiou'}
>>> words_length
{'sun': 3, 'space': 5, 'rocket': 6}
Nota
Se puede consultar el PEP-274 para ver más ejemplos sobre diccionarios por comprensión.
Ejercicio
pypas: split-marks
Objetos «hashables»¶
La única restricción que deben cumplir las claves de un diccionario es ser «hashables» [7]. Un objeto es «hashable» si se le puede asignar un valor «hash» que no cambia en ejecución durante toda su vida.
Para encontrar el «hash» de un objeto, Python usa la función hash()
, que devuelve un número entero y es utilizado para indexar la tabla «hash» que se mantiene internamente:
>>> hash(999)
999
>>> hash(3.14)
322818021289917443
>>> hash('hello')
-8103770210014465245
>>> hash(('a', 'b', 'c'))
-2157188727417140402
Para que un objeto sea «hashable», debe ser inmutable:
>>> hash(['a', 'b', 'c'])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
Nota
De lo anterior se deduce que las claves de los diccionarios, al tener que ser «hasheables», sólo pueden ser objetos inmutables.
La función «built-in» hash()
realmente hace una llamada al método mágico __hash__()
del objeto en cuestión:
>>> hash('spiderman')
-8105710090476541603
>>> 'spiderman'.__hash__()
-8105710090476541603