Python dictionaries

A dictionary is an example of a key value store also known as Mapping in Python. It allows you to store and retrieve elements by referencing a key. As dictionaries are referenced by key, they have very fast lookups. As they are primarily used for referencing items by key, they are not sorted.

Creating a dictionary

Rules to create a dictionary:

  • Every key must he unique (otherwise it will be overridden)
  • Every key must he hashable (can use the hash function to hash it; otherwise TypeError will be thrown)
  • There is no particular order for the keys
# Creating and populating it with values
stock = {'eggs': 5, 'milk': 2}

# Or creating an empty dictionary
dictionary = {}

# And populating it after
dictionary['eggs'] = 5
dictionary['milk'] = 2

# Values can also be lists
mydict = {'a': [1, 2, 3], 'b': ['one', 'two', 'three']}

# Use list.append() method to add new elements to the values list
mydict['a'].append(4) # => {'a': [1, 2, 3, 4], 'b': ['one', 'two', 'three']}
mydict['b'].append('four') # => {'a': [1, 2, 3, 4], 'b': ['one', 'two', 'three', 'four']}

# We can also create a dictionary using a list of two-items tuples
iterable = [('eggs', 5), ('milk', 2)]
dictionary = dict(iterables)

# Or using keyword argument:
dictionary = dict(eggs=5, milk=2)

# Another way will be to use the dict.fromkeys:
dictionary = dict.fromkeys((milk, eggs)) # => {'milk': None, 'eggs': None}
dictionary = dict.fromkeys((milk, eggs), (2, 5)) # => {'milk': 2, 'eggs': 5}

Using built-in class: dict

mydict1 = dict()
mydict2 = dict(key='value')
mydict3 = dict([('key', 'value')])
# make a shallow copy of another dict (only when the keys are ONLY strings!)
mydict4 = dict(**otherdict)

Creating an ordered dictionary

from collections import OrderedDict

mydict = OrderedDict()
mydict['first']  = 1
mydict['second'] = 2
mydict['third']  = 3
mydict['last']   = 4

# Outputs "first 1", "second 2", "third 3", "last 4"
for key in mydict:
    print(key, mydict[key])

Modifying a dictionary

To add items to your dictionary, you can simply create a new key with a value:

mydict['new_key'] = 'new_value'

But if you want to add more key->value pairs at once, you must use the .update() method:

mydict.update({
    'name': 'John Doe',                         # the value is a string
    'age': 25,                                  # the value is a number (integer)
    'volunteer_years': [2014, 2016, 2017],      # the value is a list
    'birth_date': {                             # the value is another dictionary
        'year': 1990,
        'month': 'June',
        'day': 10
    }
})

Deleting a dictionary item

To delete an item from a dictionary use the del statement and provide the key of the dictionary:

del mydict['volunteer_years']

Avoiding KeyError Exceptions

Many times it can happen that you will access a non-existent key, which will result in a KeyError exception:

mydict = {}
mydict['not-here']

The exception will be:

Traceback (most recent call last):
  File "/path/to/my-python-file.py", line 1, in <module>
    mydict['not-here']
KeyError: 'not-here'

To avoid this error, you must use the dict.get method which allows you to have a default value to return in case of the absent key:

value = mydict.get(key, default_value=None)

And as a working example, you'll have:

myvalue1 = mydict.get('mykey1')                     # will return None if the key does not exists
myvalue2 = mydict.get('mykey2', 'my-default-value') # will return string 'my-default-value' if the key does not exists

Other ways to bypass the KeyError exception

Catch the exception and return the default value

try:
    value = mydict[key]
except KeyError:
    value = my_default_value

You can also check if the key exists in the dictionary:

if key in mydict:
    value = mydict[key]
else:
    value = default_value

Iterating over a dictionary

If you use the dictionary as an iterator (it means in a for loop statement), it will traverse the keys of the dictionary and you will be able to get the values through the key:

mydict = {
    'a': 1,
    'b': 2,
    'c': 3,
}
for key in mydict:
   print(key, mydict[key])
# a 1
# b 2
# c 3

The same is true when usef in a comprehension

print([key for key in mydict])
# ['a', 'b', 'c']

You also can loop through both the key and value simultaneously, by using mydict.items() method:

for key, value in mydict.items():
    print(key, value)
# a 1
# b 2
# c 3

If you need only the keys, you will use the method mydict.keys() in the same manner. The same will be for the values, by using mydict.values()

The methods keys(), values(), items() will return lists, while iterkeys(), itervalues() and iteritems() will return iterators.

Dictionary with default values

This will be available in standard library as defaultdict. Being in standard library means that you don't need to install anything with pip or poetry

from collections import defaultdict

mydict = defaultdict(int)
mydict['one']
mydict['two'] = 6
mydict['three']

print(mydict) # --> defaultdict(<class 'int'>, {'one': 0, 'two': 6, 'three': 0})

As you can see, if you don't add a value to the key, by default will be 0 assigned in this case. But the same you can do with a lambda to add a string

from collections import defaultdict

mydict = defaultdict(lambda: 'mystring')
mydict['one']
mydict['two'] = 6
mydict['three'] = 'my third element'
mydict['four']

print(mydict) # --> defaultdict(<function <lambda> at 0x104abc3a0>, {'one': 'mystring', 'two': 6, 'three': 'my third element', 'four': 'mystring'})

Merging dictionaries

Consider the following dictionaries

fish = {'name': 'Nemo', 'hands': 'fins', 'special': 'gills'}
dog = {'name': 'Lassie', 'hands': 'paws', 'color': 'brown'}

fishdog = {**fish, **dog} # you can have more dictionaries here as well
# fishdog = {'name': 'Lassie', 'hands': 'paws', 'special': 'gills', 'color': 'brown'}

In this example you can see that the duplicated keys map to their latter-most value. For example "paws" overrides "fins"

If you want to keep the first values and only add new ones that don't exist, use the following example:

from collections import ChainMap
fishdog = dict(ChainMap(fish, dog))
# fishdog = {'name': 'Nemo', 'hands': 'fins', 'color': 'brown', 'special': 'gills'}
Using dict.update will override the existing keys-values with the new ones

Accesing keys and values

Let's say we have the following dictionary:

mydict = {
    'brand': 'BMW',
    'model': 'R 1250 GS',
    'year': 2022,
    'color': 'Triple Black',
    'engine': {
        'cylinders': 2,
        'capacity': 1254,
        'power': {
            'kw': 100,
            'hp': 136,
            'rev': 7750
        },
        'torque': {
            'nm': 143,
            'rev': 6250
        }
    },
    'length': {
        'number': 2207,
        'unit': 'mm'
    },
    'height': {
        'number': 1430,
        'unit': 'mm'
    },
    'width': {
        'number': 249,
        'unit': 'kg'
    },
    'packages': ['Dynamic Packet', 'Enduro Packet', 'Lights Packet', 'Comfort Packet', 'Touring Packet']
}

Here are some examples on how to access the values from the above dictionary:

# Get the Brand
print(mydict['brand']) # or mydict.get('brand')

# Get the Model
print(mydict['model'])

# Get the Engine capacity
print(mydict['engine']['capacity'])

# Get the engine HorsePower
print(mydict['engine']['power']['hp'])

# Get the third package: Lights Packet
print(mydict['packages'][2]) # yes, it is correct, list indexing starts from zero (0)