In this article, I will talk about dictionaries. This is the second article in the series named "Data Structures in Python". The first part of this series was about lists.
Dictionaries are important data structures in Python that use keys for indexing. They are an unordered sequence of items (key-value pairs), which means the order is not preserved. The keys are immutable. Just like lists, the values of dictionaries can hold heterogeneous data i.e., integers, floats, strings, NaN, Booleans, lists, arrays, and even nested dictionaries.
This article will provide you a clear understanding and enable you to work proficiently with Python dictionaries.
The following topics are covered in this article:
- Creating a dictionary and adding elements
- Accessing dictionary elements
- Removing dictionary elements
- Adding/inserting new elements
- Merging/concatenating dictionaries
- Modifying a dictionary
- Sorting a dictionary
- Dictionary comprehension
- Alternative ways to create dictionaries
- Copying a dictionary
- Renaming existing keys
- Nested dictionaries
- Checking if a key exists in a dictionary
1) Creating a dictionary and adding elements
Like lists are initialized using square brackets ([ ]), dictionaries are initialized using curly brackets ({ }). An empty dictionary has, of course, zero length.
dic_a = {} # An empty dictionary
type(dic_a)
>>> dict
len(dic_a)
>>> 0
A dictionary has two characteristic features: keys and values. Each key has a corresponding value. Both, the key and the value, can be of type string, float, integer, NaN, etc. Adding elements to a dictionary means adding a key-value pair. A dictionary is composed of one or more key-value pairs.
Let us add some elements to our empty dictionary. The following is one way to do so. Here, 'A'
is the key and 'Apple'
is its value. You can add as many elements as you like.
# Adding the first element
dic_a['A'] = 'Apple'
print (dic_a)
>>> {'A': 'Apple'}
# Adding the second element
dic_a['B'] = 'Ball'
print (dic_a)
>>> {'A': 'Apple', 'B': 'Ball'}
Note: Python being case-sensitive, 'A'
and 'a'
act as two different keys.
dic_a['a'] = 'apple'
print (dic_a)
>>> {'A': 'Apple', 'B': 'Ball', 'a': 'apple'}
Initializing dictionary all at once
If you find the above method of adding elements one-by-one tiresome, you can also initialize the dictionary at once by specifying all the key-value pairs.
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
Heterogeneous dictionary
So far your dictionary had strings as keys and values. A dictionary can also store data of mixed types. The following is a valid Python dictionary.
dic_b = {1: 'Ace', 'B': 123, np.nan: 99.9, 'D': np.nan, 'E': np.inf}
You should though use meaningful names for the keys as they denote the indices of dictionaries. In particular, avoid using floats and np.nan as keys.
2) Accessing dictionary elements
Having created our dictionaries, let us see how we can access their elements.
Accessing the keys and values
You can access the keys and values using the functions dict.keys()
and dict.values()
, respectively. You can also access both keys and values in the form of tuples using the items()
function.
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
dic_a.keys()
>>> dict_keys(['A', 'B', 'C'])
dic_a.values()
>>> dict_values(['Apple', 'Ball', 'Cat'])
dic_a.items()
>>> dict_items([('A', 'Apple'), ('B', 'Ball'), ('C', 'Cat')])
Alternatively, you can also use a "for" loop to access/print them one at a time.
# Printing keys
for key in dic_a.keys():
print (key, end=' ')
>>> A B C
#############################
# Printing values
for key in dic_a.values():
print (key, end=' ')
>>> Apple Ball Cat
You can avoid two "for" loops and access the keys and values using items()
. The "for" loop will iterate through the key-value pairs returned by items()
. Here, the key
and value
are arbitrary variable names.
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
for key, value in dic_a.items():
print (key, value)
>>> A Apple
B Ball
C Cat
Accessing individual elements
The dictionary items cannot be accessed using list-like indexing.
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
dic_a[0]
>>> ----> 1 dic_a[0]
KeyError: 0
You need to use the keys to access the corresponding values from a dictionary.
# Accessing the value "Apple"
dic_a['A']
>>> 'Apple'
# Accessing the value "Cat"
dic_a['C']
>>> 'Cat'
You will get an error if the key does not exist in the dictionary.
dic_a['Z']
>>> KeyError Traceback (most recent call last)
----> 1 dic_a['Z']
KeyError: 'Z'
If you want to avoid such key errors in case of non-existent keys, you can use the get()
function. This returns None
when the key does not exist. You can also use a custom message to be returned.
print (dic_a.get('Z'))
>>> None
# Custom return message
print (dic_a.get('Z', 'Key does not exist'))
>>> Key does not exist
Accessing dictionary elements like in a list
If you want to access dictionary elements (either keys or values) using indices, you need to first convert them into lists.
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
list(dic_a.keys())[0]
>>> 'A'
list(dic_a.keys())[-1]
>>> 'C'
list(dic_a.values())[0]
>>> 'Apple'
list(dic_a.values())[-1]
>>> 'Cat'
3) Removing dictionary elements
Deleting elements from a dictionary means deleting a key-value pair together.
Using del
You can delete the dictionary elements using the del
keyword and the key whose value you want to delete. The deletion is in-place, which means you do not need to re-assign the value of the dictionary after deletion.
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
# Deleting the key-value pair of 'A': 'Apple'
del dic_a['A']
print (dic_a)
>>> {'B': 'Ball', 'C': 'Cat'}
# Deleting the key-value pair of 'C': 'Cat'
del dic_a['C']
print (dic_a)
>>> {'B': 'Ball'}
Using pop( )
You can also use the "pop( )" function to delete elements. It returns the value being popped (deleted) and the dictionary is modified in-place.
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
dic_a.pop('A')
>>> 'Apple'
print (dic_a)
# {'B': 'Ball', 'C': 'Cat'}
In both the above-described methods, you will get a KeyError if the key to be deleted does not exist in the dictionary. In the case of "pop( )", you can specify the error message to show up if the key does not exist.
key_to_delete = 'E'
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
dic_a.pop(key_to_delete, f'Key {key_to_delete} does not exist.')
>>> 'Key E does not exist.'
Deleting multiple elements
There is no direct way but you can use a "for" loop as shown below.
to_delete = ['A', 'C']
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
for key in to_delete:
del dic_a[key]
print (dic_a)
>>> {'B': 'Ball'}
4) Adding/inserting new elements
You can add one element at a time to an already existing dictionary as shown below.
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
dic_a['D'] = 'Dog'
print (dic_a)
>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog'}
dic_a['E'] = 'Egg'
print (dic_a)
>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog', 'E': 'Egg'}
If the key you are adding already exists, the existing value will be overwritten.
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
dic_a['A'] = 'Adam' # key 'A' already exists with the value 'Apple'
print (dic_a)
>>> {'A': 'Adam', 'B': 'Ball', 'C': 'Cat'}
Using update( )
You can also use the update()
function for adding a new key-value pair by passing the pair as an argument.
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
dic_a.update({'D': 'Dog'})
print (dic_a)
>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog'}
The update()
function also allows you to add multiple key-value pairs simultaneously to an existing dictionary.
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
dic_b = {'D':'Dog', 'E':'Egg'}
dic_a.update(dic_b)
print(dic_a)
>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog', 'E': 'Egg'}
5) Merging/concatenating dictionaries
You can merge two or more dictionaries using the unpacking operator (**
) starting Python 3.5 onwards.
dic_a = {'A': 'Apple', 'B': 'Ball'}
dic_b = {'C': 'Cat', 'D': 'Dog'}
dic_merged = {**dic_a, **dic_b}
print (dic_merged)
>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog'}
If you don’t want to create a new dictionary but just want to add dic_b
to the existing dic_a
, you can simply update the first dictionary as shown earlier.
dic_a = {'A': 'Apple', 'B': 'Ball'}
dic_b = {'C': 'Cat', 'D': 'Dog'}
dic_a.update(dic_b)
print (dic_a)
>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog'}
How are duplicate keys handled while concatenating?
One of the characteristics of Python dictionaries is that they cannot have duplicate keys i.e., a key cannot appear twice. So, what happens if you concatenate two or more dictionaries that have one or more common keys.
The answer is that the key-value pair in the last merged dictionary (in the order of merging) will survive. In the following example, the key'A'
exists in all three dictionaries, and, therefore, the final dictionary took the value from the last merged dictionary (dic_c
).
dic_a = {'A': 'Apple', 'B': 'Ball'}
dic_b = {'C': 'Cat', 'A': 'Apricot'}
dic_c = {'A': 'Adam', 'E': 'Egg'}
dic_merged = {**dic_a, **dic_b, **dic_c}
print (dic_merged)
>>> {'A': 'Adam', 'B': 'Ball', 'C': 'Cat', 'E': 'Egg'}
Word of caution
I just said that the dictionaries cannot have duplicate keys. Strictly speaking, you can define a dictionary with duplicate keys, but, when you print it, only the last duplicate key will be printed. A shown below, only unique keys are returned, and for the duplicated key (‘A’ here), only the last value is returned.
dic_a = {'A': 'Apple', 'B': 'Ball', 'A': 'Apricot', 'A': 'Assault'}
print (dic_a)
>>> {'A': 'Assault', 'B': 'Ball'}
Easier way in Python 3.9+
From Python 3.9 onwards, you can use the |
operator to concatenate two or more dictionaries.
dic_a = {'A': 'Apple', 'B': 'Ball'}
dic_b = {'C': 'Cat', 'D': 'Dog'}
dic_c = dic_a | dic_b
>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog'}
# Concatenating more than 2 dictionaries
dic_d = dic_a | dic_b | dic_c
6) Modifying a dictionary
If you want to change the value of 'A'
from 'Apple'
to 'Apricot'
, you can use a simple assignment.
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
dic_a['A'] = 'Apricot'
print (dic_a)
>>> {'A': 'Apricot', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog'}
7) Sorting a dictionary
The order is not maintained in a dictionary. You can sort a dictionary using either the keys or the values using the sorted()
function.
Sorting using keys
If the keys are strings (alphabets), they will be sorted alphabetically. In a dictionary, we have two main elements: keys and values. Hence, while sorting with respect to the keys, we use the first element i.e. keys, and, therefore, the index used in the lambda function is "[0]". You can read this post for more information on the lambda functions.
dic_a = {'B': 100, 'C': 10, 'D': 90, 'A': 40}
sorted(dic_a.items(), key=lambda x: x[0])
>>> [('A', 40), ('B', 100), ('C', 10), ('D', 90)]
The sorting is not in-place. As shown below, if you now print the dictionary, it remains unordered, as initialized originally. You will have to reassign it after sorting.
# The dictionary remains unordered if you print it
print (dic_a)
>>> {'B': 100, 'C': 10, 'D': 90, 'A': 40}
If you want to sort in reverse order, specify the keyword reverse=True
.
sorted(dic_a.items(), key=lambda x: x[0], reverse=True)
>>> [('D', 90), ('C', 10), ('B', 100), ('A', 40)]
Sorting using values
To sort a dictionary based on its values, you need to use the index "[1]" inside the lambda function.
dic_a = {'B': 100, 'C': 10, 'D': 90, 'A': 40}
sorted(dic_a.items(), key=lambda x: x[1])
>>> [('C', 10), ('A', 40), ('D', 90), ('B', 100)]
8) Dictionary comprehension
It is a very helpful method for creating dictionaries dynamically. Suppose you want to create a dictionary where the key is an integer and the value is its square. A dictionary comprehension would look like the following.
dic_c = {i: i**2 for i in range(5)}
print (dic_c)
>>> {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
If you want your keys to be strings, you can use "f-strings".
dic_c = {f'{i}': i**2 for i in range(5)}
print (dic_c)
>>> {'0': 0, '1': 1, '2': 4, '3': 9, '4': 16}
9) Alternative ways to create dictionaries
Creating a dictionary from lists
Suppose you have two lists and you want to create a dictionary out of them. The simplest way is to use the dict()
constructor.
names = ['Sam', 'Adam', 'Tom', 'Harry']
marks = [90, 85, 55, 70]
dic_grades = dict(zip(names, marks))
print (dic_grades)
>>> {'Sam': 90, 'Adam': 85, 'Tom': 55, 'Harry': 70}
You can also zip the two lists together and create the dictionary using "dictionary comprehension" as shown earlier.
dic_grades = {k:v for k, v in zip(names, marks)}
print (dic_grades)
>>> {'Sam': 90, 'Adam': 85, 'Tom': 55, 'Harry': 70}
Passing key-value pairs
You can also pass a list of key-value pairs separated by commas to the dict()
construct and it will return a dictionary.
dic_a = dict([('A', 'Apple'), ('B', 'Ball'), ('C', 'Cat')])
print (dic_a)
>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
If your keys are strings, you can use even a simpler initialization using only the variables as the keys.
dic_a = dict(A='Apple', B='Ball', C='Cat')
print (dic_a)
>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
10) Copying a dictionary
I will explain this point using a simple example. There are more subtleties involved in the copying mechanism of dictionaries, and, I would recommend the reader to refer to this Stack Overflow post for a detailed explanation.
Reference assignment
When you simply reassign an existing dictionary (parent dictionary) to a new dictionary, both point to the same object ("reference assignment").
Consider the following example where you reassign dic_a
to dic_b
.
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
dic_b = dic_a # Simple reassignment (Reference assignment)
Now, if you modify dic_b
(for e.g. adding a new element), you will notice that the change will also be reflected in dic_a
.
dic_b['D'] = 'Dog'
print (dic_b)
>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog'}
print (dic_a)
>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog'}
Shallow Copy
A shallow copy is created using the copy()
function. In a shallow copy, the two dictionaries act as two independent objects, with their contents still sharing the same reference. If you add a new key-value pair in the new dictionary (shallow copy), it will not show up in the parent dictionary.
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
dic_b = dic_a.copy()
dic_b['D'] = 'Dog'
# New, shallow copy, has the new key-value pair
print (dic_b)
>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog'}
# The parent dictionary does not have the new key-value pair
print (dic_a)
>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
Now, if the contents in the parent dictionary ("dic_a") will change depends on the type of the value. For instance, in the following, the contents are simple strings that are immutable. So changing the value in "dic_b" for a given key ('A'
in this case) will not change the value of the key 'A'
in "dic_a".
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
dic_b = dic_a.copy()
# Replace an existing key with a new value in the shallow copy
dic_b['A'] = 'Adam'
print (dic_b)
>>> {'A': 'Adam', 'B': 'Ball', 'C': 'Cat'}
# Strings are immutable so 'Apple' doesn't change to 'Adam' in dic_a
print (dic_a)
>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
However, if the value of the key 'A'
in "dic_a" is a list, then changing its value in the "dic_b" will reflect the changes in "dic_a" (parent dictionary), because lists are mutable.
dic_a = {'A': ['Apple'], 'B': 'Ball', 'C': 'Cat'}
# Make a shallow copy
dic_b = dic_a.copy()
dic_b['A'][0] = 'Adam'
print (dic_b)
>>> {'A': ['Adam'], 'B': 'Ball', 'C': 'Coal'}
# Lists are mutable so the changes get reflected in dic_a too
print (dic_a)
>>> {'A': ['Adam'], 'B': 'Ball', 'C': 'Cat'}
11) Renaming existing keys
Suppose you want to replace the key "Adam" by "Alex". You can use the pop
function because it deletes the passed key ("Adam" here) and returns the deleted value (85 here). So you kill two birds with a single shot. Use the returned (deleted) value to assign the value to the new key ("Alex" here). There can be more complicated cases where the key is a tuple. Such cases are out of the scope of this article.
dic_a = {'Sam': 90, 'Adam': 85, 'Tom': 55, 'Harry': 70}
dic_a['Alex'] = dic_a.pop('Adam')
print (dic_a)
>>> {'Sam': 90, 'Tom': 55, 'Harry': 70, 'Alex': 85}
12) Nested dictionaries
A nested dictionary has one or more dictionaries within a dictionary. The following is the simplest example of a nested dictionary with two layers of nesting. Here, the outer dictionary (layer 1) has only one key-value pair. However, the value is now a dictionary itself.
dic_a = {'A': {'B': 'Ball'}}
dic_a['A']
>>> {'B': 'Ball'}
type(dic_a['A'])
>>> dict
If you want to further access the key-value pair of the inner dictionary (layer 2), you now need to use dic_a['A']
as the dictionary.
dic_a['A']['B']
>>> 'Ball'
Three-layered dictionary
Let us add an additional layer of the nested dictionary. Now, dic_a['A']
is a nested dictionary in itself, unlike the simplest nested dictionary above.
dic_a = {'A': {'B': {'C': 'Cat'}}}
# Layer 1
dic_a['A']
>>> {'B': {'C': 'Cat'}}
# Layer 2
dic_a['A']['B']
>>> {'C': 'Cat'}
# Layer 3
dic_a['A']['B']['C']
>>> 'Cat'
13) Checking if a key exists in a dictionary
You can find if a particular key exists in a dictionary using the in
operator.
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog'}
'A' in dic_a
# True
'E' in dic_a
# False
In the above code, you do not need to use "in dic_a.keys( )" because "in dic_a" already looks up in the keys.
This brings me to the end of this article. You can access the first part of this series on "Data Structures in Python" here.