How to use dictionary data structure in Python?

There are many useful programming data structures which most developers use every single day. Python, like any other major language, offers great implementations of those structures – efficient and convenient to use. Let’s look at the Python dictionary data structure which is equivalent to maps/hashmaps in other languages.

Definition

It’s useful to think of a dictionary as a set of key-value pairs. Each value we’re interested in is identified by a unique key that allows us to find the value quickly. This key-value relationship comes up all the time both in real life and in computer-related contexts. For instance:

  • phone number (key) corresponds to a person (value),
  • user ID (key) in a database identifies a single user (value).

Let’s see some examples.

Examples

In the examples on this page, you’ll see the code as it looks like in Python shell (Python REPL), which means it can start with >>> prompt and have ... at the beginning of some lines. If you run this code outside of Python shell, you’ll need to remove them!

In Python shell we can enter this code:

1>>> dict1 = {1: "Tom", 5: "Eve", 981: "Anne"}

We’ve defined a new dictionary, named dict1 with three different key-value pairs. The keys are integers (whole numbers) and values are strings (text). In this simple case, keys could be user IDs and values could be users.

We can access each value using the [] operator like this:

1>>> print(dict1[5])
2Eve
3>>> print(dict1[981])
4Anne
5>>> print(dict1[0])
6Traceback (most recent call last):
7  File "<stdin>", line 1, in <module>
8KeyError: 0

We access and print values identified by keys 5 and 981. However, we come across an error if we try accessing value with key 0 (this is, of course, because we haven’t added it to our dictionary!). Notice the KeyError exception raised in such case.

If we expect that a value might not be present, we can check it explicitly:

1>>> if 0 in dict1:
2...     print("Key 0 is present")
3... else:
4...     print("Key 0 is not present")
5...
6Key 0 is not present

Notice the use of the in operator which we can use to check if requested value is present or not.

Alternatively, we can use the following shortcut. Dictionaries offer get function which won’t raise an exception in case a key is not present, but instead will return a default value (None by default but we can choose our own, too).

1>>> print(dict1.get(0))
2None
3>>> print(dict1.get(0, "0 not found - default value"))
40 not found - default value

Types in dictionaries

So far we’ve seen one simple example where our keys were all integers and values were all strings. However, due to Python’s type system’s flexibility we can use a wide range of types in our dictionaries. We can even mix different types in a single dictionary.

In the following case, we use strings as our keys and values.

1dict2 = {"a": "apple", "b": "banana", "c": "cherry"}

What if we wanted to modify our dictionary and add a new key-value pair of a different type? Well, it’s very simple:

1>>> dict2[0] = "not a fruit"
2>>> print(dict2[0])
3not a fruit
4>>> print(dict2["b"])
5banana

We use the [] operator to assign new values to a dictionary. And as you can see above, we’ve now added another string value to the dict2 dictionary – but this time identified by an integer.

What if we made a mistake and wanted to delete something from a dictionary? Let’s use del for this purpose.

1>>> del dict2[0]
2>>> print(dict2[0])
3Traceback (most recent call last):
4  File "<stdin>", line 1, in <module>
5KeyError: 0

After deleting something with del we cannot access the value anymore, and we get a KeyError exception as expected.

Limitations of types in dictionaries

So does that mean we can put anything in a dictionary? Well, not exactly. Python states that we can use any immutable type as a key. This means we cannot use any type that can be modified over its lifetime. For example, dictionaries or lists cannot be used as keys. Some mutable types have immutable equivalents that can be used as keys in dictionaries. For example, we can’t use lists or sets as keys but instead we can use tuples or frozensets. Let’s see examples:

 1# Let's define a list 'a' and a set 'b'
 2>>> a = [1,2,3]
 3>>> b = {1,2,3}
 4# Using them as keys will not work:
 5>>> d = {a: 1}
 6Traceback (most recent call last):
 7  File "<stdin>", line 1, in <module>
 8TypeError: unhashable type: 'list'
 9>>> d = {b: 1}
10Traceback (most recent call last):
11  File "<stdin>", line 1, in <module>
12TypeError: unhashable type: 'set'
13# So instead let's use a tuple and a frozenset
14>>> c = tuple(a)
15>>> d = {c:1}
16>>> d
17{(1, 2, 3): 1}
18# And let's access it using a tuple as key
19>>> d[(1,2,3)]
201
21# And similarly with the frozenset
22>>> d = frozenset(b)
23>>> d = {d: 1}
24>>> d
25{frozenset({1, 2, 3}): 1}

So, in short, everything works as expected as long as we remember to use immutable types as keys. Having said this, this limitation only applies to keys, there’s no problem having mutable types as values. For example, we can identify this list by a string:

1>>> d = {}
2>>> d["list"] = [1,2,3,4]
3>>> d
4{'list': [1, 2, 3, 4]}
5>>> d["list"]
6[1, 2, 3, 4]

Common operations on dictionaries

There’s a number of operations we often see performed on dictionaries. One such operation is iterating over the contents of a python dictionary.

 1# Let's define a dictionary
 2d = {"a": "apple", "b": "banana", "r": "raspberry", "m": "melon"}
 3# Let's iterate over the contents of the dictionary.
 4# We use the `items()` function to get the key-value pairs and we assign them to `letter`, and `fruit` variables.
 5>>> for letter, fruit in d.items():
 6...     print(letter, " - ", fruit)
 7a  -  apple
 8b  -  banana
 9r  -  raspberry
10m  -  melon

It’s also very easy to get only keys or only values from a dictionary. Just use keys() or values() methods! In the following examples, we obtain values and keys and request them as lists.

1>>> list(d.values())
2['apple', 'banana', 'raspberry', 'melon']
3>>> list(d.keys())
4['a', 'b', 'r', 'm']

Other benefits of dictionaries

Something we haven’t really touched on yet and which is a tremendous benefit of dictionaries. Performance of dictionaries is top-notch and this is due to the fact that accessing dictionaries takes O(1) time (when expressed using the Big-O notation). That means it takes a constant amount of time regardless of the size of a dictionary. Contrast it with lists where accessing a list takes linear time (O(n) in Big-O notation) – which means it is proportional to the size of a list. This makes searching large lists very slow. In comparison, looking up data in dictionaries is cheap.