Python Basics #10 — Dictionaries

11 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:

Define an empty dictionary
>>> my_dict = {}
>>> my_dict
{}

Check the type:

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:

Define a dictionary
>>> 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:

Define with the 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:

Syntax error example
>>> 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:

Syntax error example
>>> 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:

Access a value
>>> person['age']
25

Now change the age:

Change a value
>>> 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:

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:

Delete an 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:

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

Try deleting a missing key:

KeyError example
>>> 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:

Key data types
>>> 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:

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

Try a dict:

Dict key error
>>> 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:

Value data types
>>> 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']:

Nested access
>>> 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:

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:

Check the order
>>> 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:

Add an item
>>> 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+:

Check the order
>>> 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:

Add an 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:

Check help(dict)
>>> 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:

clear method
>>> 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:

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

Now pass a key that doesn’t exist:

get method
>>> 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:

get method
>>> 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:

Check the iterator
>>> 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:

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

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

items method
>>> 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:

items method
>>> 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 loop output
>>> for i in my_dict:
...     print(my_dict[i])
...
2
1
3

Output is unordered.

Now sort the keys before iterating:

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

Use sorted to sort:

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

Iterate using sorted keys:

Print with 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:

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

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

Define two dictionaries and join them:

update method
>>> 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