A Python dictionary is an implementation of what you may know as a hash table or associative array. A dictionary consists of a collection of key-value pairs. When you query a dictionary by giving a key, the dictionary returns the associated value.
Defining dictionaries
A dictionary can be defined by listing its key:value pairs within braces (curly brackets), as in:
ukDict = { 'Aberdeen': 'Scotland', 'Dundee': 'Scotland', 'London': 'England', 'Cardiff': 'Wales' }
Here, the keys are all cities within the United Kingdom, and the values are the countries within the UK in which each city is located. A key must be unique, as it serves as an index by which a value is retrieved. If the same key mapped to two or more different values, the dictionary wouldn’t know which value to retrieve.
Values need not be unique, as illustrated in this example, where two cities map to the same location (Scotland).
The keys must be immutable data types, so they can be any primitive data type such as ints, floats, strings or tuples. You cannot use lists as keys, since a list is mutable (its contents can be modified).
The values, however, can be anything, mutable or immutable. Thus you can map ints to lists, for example. This allows the value to which a key maps to be altered as a program runs.
The key:value pairs are ordered within the dictionary, as of Python version 3.7. Earlier versions did not impose an order on the key:value pairs.
To access a value, we pass the correct key to the dictionary. For example, if we want to find the country in which Aberdeen is located, we can type ukDict['Aberdeen']
, which returns the value ‘Scotland’.
One aspect of dictionary access can be a bit confusing. This arises because, as we mentioned, any immutable data type can be used as keys. Since ints are immutable, we can define a dictionary in which ints are used as keys, as in:
award = {3: 'bronze', 2: 'silver', 1: 'gold', 4: 'no medal' }
We can use this dictionary to discover which type of medal a contestant is awarded. This is done in the usual way, by giving the dictionary name and the key in square brackets, so we have award[1]
returning ‘gold’, award[2]
returning ‘silver’ and so on. Note that award[1]
does not return the first (or second, if we start counting at 0] element in the dictionary; the [1]
index refers to the key used to store the string ‘gold’ in the dictionary, and not to the location of a particular key:value pair. Thus the syntax we used with lists to obtain slices or ranges of list elements do not apply to dictionaries.
In general, we cannot use the numerical location of a key:value pair to access it; we must always use a key.
Although the notation award[2]
is a concise way of accessing a value, it does have the drawback that if a non-existent key is passed to the dictionary, the result is an error. If you’re unsure whether a given key is in the dictionary, you can use the get()
method instead of the square bracket notation. get()
contains two arguments: the first is the key, and the second (optional) argument is what to return if that key doesn’t exist. Thus, we can give the command award.get(5, 'No such medal')
. This results in the string ‘No such medal’ being returned, whereas award[5]
would stop the program with an error.
Modifying dictionaries
Although the keys within a dictionary must be immutable, the dictionary itself is mutable, in the sense that it can be modified by adding or deleting elements, and the value to which a key refers can also be modified. To add a key:value pair, we can do the following:
ukDict = { 'Aberdeen': 'Scotland', 'Dundee': 'Scotland', 'London': 'England', 'Cardiff': 'Wales' } ukDict['Belfast'] = 'Northern Ireland' ukDict['Cambridge'] = 'England'
This adds Belfast and Cambridge as two more keys with their associated values. It’s important to remember that keys must be unique. If two or more key:value pairs with the same key are added to a dictionary, only the last one is stored. Suppose the above code was modified to:
ukDict = { 'Aberdeen': 'Scotland', 'Dundee': 'Scotland', 'London': 'England', 'Cardiff': 'Wales' } ukDict['Belfast'] = 'Northern Ireland' ukDict['Cambridge'] = 'Britain' ukDict['Cambridge'] = 'England'
After all additions are processed, the dictionary would contain the pair ‘Cambridge’:’England’, but not ‘Cambridge’:’Britain’.
To remove a key:value pair, we can use the pop()
function. pop()
takes two arguments, the second of which is optional. The first argument is the key you want to delete. The second argument is what pop()
should return if the key isn’t found. If you call pop()
with only one argument and that key doesn’t exist, you’ll get an error.
With the above example, we could have
ukDict = { 'Aberdeen': 'Scotland', 'Dundee': 'Scotland', 'London': 'England', 'Cardiff': 'Wales' } ukDict['Belfast'] = 'Northern Ireland' ukDict['Cambridge'] = 'England' ukDict.pop('Dundee') ukDict.pop('Wibble', 'No such city') ukDict.pop('Aberdeen', 'No such city') ukDict.pop('Wibble')
The first pop()
succeeds because Dundee is a valid key, so it gets removed and the corresponding value ‘Scotland’ is returned. The second pop()
fails to find a city called ‘Wibble’ so it returns the string ‘No such city’. The next pop()
succeeds in removing Aberdeen, and returns ‘Scotland’. The final pop()
fails and causes an error, since no return value is specified.
Lists of keys and values
Although individual elements within a dictionary can be accessed only by giving their corresponding keys, it is possible to get a list of all the keys or all the values in a dictionary. The keys()
function (no arguments) returns a list of all the keys, and values()
(also no arguments) returns a list of all the values. Well, almost a list; actually these functions return an ordered collection of keys or values which can be cast into a list, set or tuple. For example:
ukDict = { 'Aberdeen': 'Scotland', 'Dundee': 'Scotland', 'London': 'England', 'Cardiff': 'Wales' } ukDict['Belfast'] = 'Northern Ireland' ukDict['Cambridge'] = 'England' cities = list(ukDict.keys()) areas = list(ukDict.values())
cities
is now the list [‘Aberdeen’, ‘Dundee’, ‘London’, ‘Cardiff’, ‘Belfast’, ‘Cambridge’] and areas
is [‘Scotland’, ‘Scotland’, ‘England’, ‘Wales’, ‘Northern Ireland’, ‘England’]. If a value appears more than once in a dictionary, the list will contain that value multiple times as well. If you want a collection of unique values, cast the result into a set, as in areaSet = set(ukDict.values())
. You can also get a list of key:value pairs with the items()
method, as in ukPairs = list(ukDict.items())
. This gives you a list of tuples, where each tuple contains one key and its associated value.
It’s only in cases like these where the order (or lack thereof) of the items in a dictionary makes a difference. The lists of keys or values will be the same as that of the key:value pairs in the original dictionary if you’re using Python 3.7 or later, but not necessarily the same order if you’re using an earlier version of Python.
Exercise
Write a program which creates a dictionary in which the keys are the ASCII characters corresponding to the integers from 0 to 127 and the values are all initialized to 0. (You can use the built-in function chr(i)
to convert an int i to an ASCII character.) The user should then be prompted to enter a string, and the program will count the number of times each character appears in that string. Finally, print out a list of tuples showing each character and its count.
ascii = {} for i in range(0, 128): ascii[chr(i)] = 0 while True: char = input('Enter a string or \'quit\':') if char == 'quit': break for x in char: ascii[x] += 1 counts = list(ascii.items()) for c in counts: print(c)
This program prints each tuple on a separate line, which results in 128 lines being printed. You can replace the last 2 lines with print(counts)
if you want the whole thing on a single (long) line.