Python Basics #10 — Dictionaries

10 min read

The dictionary, alongside the list, is one of the most-used data structures in Python. Its defining characteristic: it uses key / value mapping. And unlike sequence types like string, list, or tuple, dictionaries are unordered (with one nuance we’ll get to). Let’s explore this in detail.

First, how to define a dictionary. Dictionaries use curly braces. Let’s create an empty dict:

>>> my_dict = {}
>>> my_dict
{}

Check the type:

>>> type(my_dict)
<class 'dict'>

It’s a dictionary. Each item has a key: value form, with key and value separated by a colon, and items separated by commas.

Define a small dictionary using curly braces:

>>> person = {
...     'first_name': 'Sanghee',
...     'last_name': 'Lee',
...     'age': 25
... }
>>> person
{'first_name': 'Sanghee', 'last_name': 'Lee', 'age': 25}

Another way to define a dictionary is to use the class constructor:

>>> person_2 = dict(
...     first_name='Sanghee',
...     last_name='Lee',
...     age=25
... )
>>> person_2
{'first_name': 'Sanghee', 'last_name': 'Lee', 'age': 25}

When using the constructor, you don’t put a colon between key and value — instead, you assign with = (like assigning to a variable). And because the key behaves like a variable name, you must not wrap it in quotes. If you do, you get a syntax error:

>>> my_dict = dict('key'='value')
File "<stdin>", line 1
my_dict = dict('key'='value')
^
SyntaxError: expression cannot contain assignment, perhaps you meant "=="?

Using a colon instead of = also raises a syntax error:

>>> my_dict = dict(key: 'value')
File "<stdin>", line 1
my_dict = dict(key: 'value')
^
SyntaxError: invalid syntax

💡 Today’s tip

Don’t use dict as a variable name to hold a dictionary. Doing so overwrites the built-in dict type with your dictionary object, and you lose access to the dict constructor.

To access or modify an item, use its key. Print the value 25 using the age key:

>>> person['age']
25

Now change the age:

>>> person['age'] = 30
>>> person
{'first_name': 'Sanghee', 'last_name': 'Lee', 'age': 30}

The age value is now 30. You can also add new items or remove existing ones.

Add an item:

>>> person['job'] = 'programmer'
>>> person
{'first_name': 'Sanghee', 'last_name': 'Lee', 'age': 30, 'job': 'programmer'}

Use the built-in del to remove the added item:

>>> del person['job']
>>> person
{'first_name': 'Sanghee', 'last_name': 'Lee', 'age': 30}

The added item is gone.

When accessing or deleting items, using a key that doesn’t exist raises a KeyError. Watch out.

Try referencing a missing key:

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

Try deleting a missing key:

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

Allowed key types in a dictionary: int, float, str, tuple.

Use each as a key:

>>> my_dict = {
...     1: 'int',
...     1.23: 'float',
...     '123': 'string',
...     (1, 2, 3): 'tuple'
... }
>>> my_dict
{1: 'int', 1.23: 'float', '123': 'string', (1, 2, 3): 'tuple'}

But list and dict cannot be used as keys. Try a list:

>>> d = {[1, 2, 3]: 'list'}
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list

Try a dict:

>>> d = {{1: 1}: 'dict'}
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'

What about the values?

Like list elements, a dictionary’s values can be of any data type. Let’s confirm:

>>> my_dict = {
...     'int': 1,
...     'float': 1.23,
...     'str': 'string',
...     'list': [1, 2, 3],
...     'tuple': (1, 2, 3),
...     'dict': {'one': 1, 'two': 2}
... }
>>> my_dict
{'int': 1, 'float': 1.23, 'str': 'string', 'list': [1, 2, 3], 'tuple': (1, 2, 3), 'dict': {'one': 1, 'two': 2}}

All data types stored.

To access a list item or a nested dictionary value inside a dict, use chained brackets like my_dict['key1']['key2']:

>>> my_dict['list'][0]
1
>>> my_dict['dict']['one']
1

Earlier I said dictionaries are unordered. Sequence types like lists store data in the order it was defined and always preserve that order, which is why you can use indexes and iterate predictably. Dictionaries don’t preserve order — items can move around when you add new ones. However, starting with Python 3.7 dictionaries do remember insertion order, and new items are appended at the end, just like lists.

Let’s first see this in a Python version below 3.7. Check the version:

>>> import sys
>>> print(sys.version)
3.5.6 |Anaconda, Inc.| (default, Aug 26 2018, 16:05:27) [MSC v.1900 64 bit (AMD64)]

Version 3.5.6.

Define a dictionary and print the stored data:

>>> my_dict = {
...     'key_1': 1,
...     'key_2': 2,
...     'key_3': 3,
... }
>>> my_dict
{'key_3': 3, 'key_2': 2, 'key_1': 1}

Output is in a different order than insertion.

Add a new item and print again:

>>> my_dict['key_4'] = 4
>>> my_dict
{'key_1': 1, 'key_4': 4, 'key_2': 2, 'key_3': 3}

💡 Today’s tip

Below Python 3.5, dictionaries are unordered, so you can’t predict where a newly added item will land.

The new item didn’t go to the end.

Now let’s do the same in Python 3.7+:

>>> import sys
>>> print(sys.version)
3.9.5 (tags/v3.9.5:0a7dcbd, May  3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)]
>>> my_dict = {
...     'key_1': 1,
...     'key_2': 2,
...     'key_3': 3,
... }
>>> my_dict
{'key_1': 1, 'key_2': 2, 'key_3': 3}

Items print in the same order they were defined.

Add a new item:

>>> my_dict['key_4'] = 4
>>> my_dict
{'key_1': 1, 'key_2': 2, 'key_3': 3, 'key_4': 4}

The new item is at the end.

Now let’s look at the class methods the dictionary class provides.

Use help to see what methods are defined:

>>> help(dict)
Help on class dict in module builtins:

class dict(object)
|  dict() -> new empty dictionary
|  dict(mapping) -> new dictionary initialized from a mapping object's
|      (key, value) pairs
|  dict(iterable) -> new dictionary initialized as if via:
|      d = {}
|      for k, v in iterable:
|          d[k] = v
|  dict(**kwargs) -> new dictionary initialized with the name=value pairs
|      in the keyword argument list.  For example:  dict(one=1, two=2)
|
|  Methods defined here:
|
|  __contains__(self, key, /)
|      True if the dictionary has the specified key, else False.
|
|  __delitem__(self, key, /)
|      Delete self[key].
|
|  __eq__(self, value, /)
|      Return self==value.
|
|  __ge__(self, value, /)
|      Return self>=value.
|
|  __getattribute__(self, name, /)
|      Return getattr(self, name).
|
|  __getitem__(...)
|      x.__getitem__(y) <==> x[y]
|
|  __gt__(self, value, /)
|      Return self>value.
|
|  __init__(self, /, *args, **kwargs)
|      Initialize self.  See help(type(self)) for accurate signature.
|
|  __ior__(self, value, /)
|      Return self|=value.
|
|  __iter__(self, /)
|      Implement iter(self).
|
|  __le__(self, value, /)
|      Return self<=value.
|
|  __len__(self, /)
|      Return len(self).
|
|  __lt__(self, value, /)
|      Return self<value.
|
|  __ne__(self, value, /)
|      Return self!=value.
|
|  __or__(self, value, /)
|      Return self|value.
|
|  __repr__(self, /)
|      Return repr(self).
|
|  __reversed__(self, /)
|      Return a reverse iterator over the dict keys.
|
|  __ror__(self, value, /)
|      Return value|self.
|
|  __setitem__(self, key, value, /)
|      Set self[key] to value.
|
|  __sizeof__(...)
|      D.__sizeof__() -> size of D in memory, in bytes
|
|  clear(...)
|      D.clear() -> None.  Remove all items from D.
|
|  copy(...)
|      D.copy() -> a shallow copy of D
|
|  get(self, key, default=None, /)
|      Return the value for key if key is in the dictionary, else default.
|
|  items(...)
|      D.items() -> a set-like object providing a view on D's items
|
|  keys(...)
|      D.keys() -> a set-like object providing a view on D's keys
|
|  pop(...)
|      D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
|
|      If key is not found, default is returned if given, otherwise KeyError is raised
|
|  popitem(self, /)
|      Remove and return a (key, value) pair as a 2-tuple.
|
|      Pairs are returned in LIFO (last-in, first-out) order.
|      Raises KeyError if the dict is empty.
|
|  setdefault(self, key, default=None, /)
|      Insert key with a value of default if key is not in the dictionary.
|
|      Return the value for key if key is in the dictionary, else default.
|
|  update(...)
|      D.update([E, ]**F) -> None.  Update D from dict/iterable E and F.
|      If E is present and has a .keys() method, then does:  for k in E: D[k] = E[k]
|      If E is present and lacks a .keys() method, then does:  for k, v in E: D[k] = v
|      In either case, this is followed by: for k in F:  D[k] = F[k]
|
|  values(...)
|      D.values() -> an object providing a view on D's values
|
|  ----------------------------------------------------------------------
|  Class methods defined here:
|
|  __class_getitem__(...) from builtins.type
|      See PEP 585
|
|  fromkeys(iterable, value=None, /) from builtins.type
|      Create a new dictionary with keys from iterable and values set to value.
|
|  ----------------------------------------------------------------------
|  Static methods defined here:
|
|  __new__(*args, **kwargs) from builtins.type
|      Create and return a new object.  See help(type) for accurate signature.
|
|  ----------------------------------------------------------------------
|  Data and other attributes defined here:
|
|  __hash__ = None

Let’s look at the most commonly used methods.

First, clear removes all items from a dictionary. Define one and clear it:

>>> my_dict = {
...     'one': 1,
...     'two': 2,
...     'three': 3
... }
>>> my_dict
{'one': 1, 'two': 2, 'three': 3}
>>> my_dict.clear()
>>> my_dict
{}

All items removed.

Next, get. The get method returns the value for the key if the key exists, and None otherwise. It’s commonly used to avoid KeyError when accessing a key that may not exist.

Define a dictionary and use get:

>>> my_dict = {
...     'key': 'value'
... }
>>> my_dict.get('key')
'value'

Now pass a key that doesn’t exist:

>>> my_dict.get('no_key')
>>>

The Python console (and Jupyter Notebook) doesn’t print anything when the return value is empty or None.

Use print to confirm None is returned:

>>> print(my_dict.get('no_key'))
None

Next, items. Python dictionaries are iterable objects you can use in a for loop. Define a dictionary and verify it’s iterable:

>>> my_dict = {
...     'one': 1,
...     'two': 2,
...     'three': 3
... }
>>>
>>> iter(my_dict)
<dict_keyiterator object at 0x000002136D0E9B30>

It’s iterable. Iterate items with a for loop:

>>> for i in my_dict.items():
...     print(i)
...
('one', 1)
('two', 2)
('three', 3)

Each item is wrapped in parentheses, suggesting tuples. Confirm:

>>> for i in my_dict.items():
...     print(type(i))
...
<class 'tuple'>
<class 'tuple'>
<class 'tuple'>

Typically, when iterating with items you unpack the key and value into separate variables:

>>> for k, v in my_dict.items():
...     print('key: {}, value: {}'.format(k, v))
...
key: one, value: 1
key: two, value: 2
key: three, value: 3

Next, keys. This returns all the dict’s keys as a list-like view. Useful when you need to access data in a specific (sorted) key order.

Define a dict and iterate it (note: iterating a dict yields keys):

>>> for i in my_dict:
...     print(my_dict[i])
...
2
1
3

Output is unordered.

Now sort the keys before iterating:

>>> my_keys = my_dict.keys()
>>> my_keys
dict_keys(['b', 'a', 'c'])

Use sorted to sort:

>>> my_keys = sorted(my_keys)
>>> my_keys
['a', 'b', 'c']

Iterate using sorted keys:

>>> for i in my_keys:
...     print(my_dict[i])
...
1
2
3

Numbers are printed in order.

Next, values. Returns all values as a list-like view.

Print all values:

>>> my_dict.values()
dict_values([2, 1, 3])

Next, update. Joins two dictionaries into one — very handy.

Define two dictionaries and join them:

>>> dict_a = {'key_a': 'value_a'}
>>> dict_b = {'key_b': 'value_b'}
>>> dict_a.update(dict_b)
>>> dict_a
{'key_a': 'value_a', 'key_b': 'value_b'}

The two dictionaries are now joined.

That’s it for today’s lesson. This is a basics course, so I covered fundamentals only. We’ll cover practical dictionary patterns in real programs in later lessons.

Next, we’ll cover sets. Sets aren’t used as much as lists or dictionaries, but they’re really handy when you know how to use them.

X