All of the compound data types we have studied in detail so far — strings, lists, and tuples — are sequence types, which use integers as indices to access the values they contain within them.
Dictionaries are yet another kind of compound type. They are Python’s built-in mapping type. They map keys, which can be any immutable type, to values, which can be any type (heterogeneous), just like the elements of a list or tuple. In other programming languages, they are called associative arrays since they associate a key with a value.
As an example, we will create a dictionary to translate English words into Spanish. For this dictionary, the keys are strings.
One way to create a dictionary is to start with the empty dictionary and add key:value pairs. The empty dictionary is denoted {}:
eng2sp = {}
eng2sp["one"] = "uno"
eng2sp["two"] = "dos"
The first assignment creates a dictionary named eng2sp; the other assignments add new key:value pairs to the dictionary. We can print the current value of the dictionary in the usual way:
eng2sp = {}
eng2sp["one"] = "uno"
eng2sp["two"] = "dos"
print(eng2sp)
The key:value pairs of the dictionary are separated by commas. Each pair contains a key and a value separated by a colon.
Another way to create a dictionary is to provide a list of key:value pairs using the same syntax as the previous output:
eng2sp = {"one": "uno", "two": "dos", "three": "tres"}
print(eng2sp)
It doesn’t matter what order we write the pairs. The values in a dictionary are accessed with keys, not with indices, so there is no need to care about ordering.
Here is how we use a key to look up the corresponding value:
eng2sp = {"one": "uno", "two": "dos", "three": "tres"}
print(eng2sp["two"])
The key "two" yields the value "dos".
Lists, tuples, and strings have been called sequences, because their items occur in order. The dictionary is the first compound type that we’ve seen that is not a sequence, so we can’t index or slice a dictionary.
The del statement removes a key:value pair from a dictionary. For example, the following dictionary contains the names of various fruits and the number of each fruit in stock:
inventory = {"apples": 430, "bananas": 312, "oranges": 525, "pears": 217}
print(inventory)
If someone buys all of the pears, we can remove the entry from the dictionary:
inventory = {"apples": 430, "bananas": 312, "oranges": 525, "pears": 217}
print(inventory)
del inventory["pears"]
print(inventory) # now there are no pears
Or if we’re expecting more pears soon, we might just change the value associated with pears:
inventory = {"apples": 430, "bananas": 312, "oranges": 525, "pears": 217}
print(inventory)
inventory["pears"] = 0
print(inventory) # now there are 0 pears
A new shipment of bananas arriving could be handled like this:
inventory = {"apples": 430, "bananas": 312, "oranges": 525, "pears": 217}
print(inventory)
inventory["bananas"] += 200
print(inventory) # now there are more bananas
The len function also works on dictionaries; it returns the number of key:value pairs:
inventory = {"apples": 430, "bananas": 312, "oranges": 525, "pears": 217}
print(len(inventory))
Dictionaries have a number of useful built-in methods.
The keys method returns what Python 3 calls a view of its underlying keys. A view object has some similarities to the range object we saw earlier — it is a lazy promise, to deliver its elements when they’re needed by the rest of the program. We can iterate over the view, or turn the view into a list like this:
eng2sp = {"one": "uno", "two": "dos", "three": "tres"}
for k in eng2sp.keys(): # The order of the k's is not defined
print("Got key "+str(k)+" which maps to value "+str(eng2sp[k]))
ks = list(eng2sp.keys())
print(ks)
It is so common to iterate over the keys in a dictionary that we can omit the keys method call in the for loop — iterating over a dictionary implicitly iterates over its keys:
eng2sp = {"one": "uno", "two": "dos", "three": "tres"}
for k in eng2sp: # loop over keys of eng2sp
print("Got key "+str(k)+" which maps to value "+str(eng2sp[k]))
The values method is similar; it returns a view object which can be turned into a list:
eng2sp = {"one": "uno", "two": "dos", "three": "tres"}
print(eng2sp.values())
The items method also returns a view, which promises a list of tuples — one tuple for each key:value pair:
eng2sp = {"one": "uno", "two": "dos", "three": "tres"}
print(eng2sp.items())
Tuples are often useful for getting both the key and the value at the same time while we are looping:
eng2sp = {"one": "uno", "two": "dos", "three": "tres"}
for (k,v) in eng2sp.items():
print("Got "+str(k)+" that maps to "+str(v))
The in and not in operators can test if a key is in the dictionary:
eng2sp = {"one": "uno", "two": "dos", "three": "tres"}
print("one" in eng2sp) # should be True
print("six" in eng2sp) # should be False
print("tres" in eng2sp) # should be False -- only looks at keys
Looking up a non-existent key in a dictionary causes a runtime error:
eng2sp = {"one": "uno", "two": "dos", "three": "tres"}
print(eng2sp["dog"]) # will generate an error since "dog" is not a key
We can get around this error by using the get method. This method will return the associated value if the key is in the dictionary, but we can tell it to return something else if the key is not in the dictionary:
eng2sp = {"one": "uno", "two": "dos", "three": "tres"}
print(eng2sp.get("three","Not in dictionary!")) # will print three's value
print(eng2sp.get("dog","Not in dictionary!")) # will print the message
As in the case of lists, because dictionaries are mutable, we need to be aware of aliasing. Whenever two variables refer to the same object, changes to one affect the other.
If we want to modify a dictionary and keep a copy of the original, use the copy method. For example, opposites is a dictionary that contains pairs of opposites:
opposites = {"up": "down", "right": "wrong", "yes": "no"}
alias = opposites
copy = opposites.copy() # Shallow copy
alias and opposites refer to the same object; copy refers to a fresh copy of the same dictionary. If we modify alias, opposites is also changed:
opposites = {"up": "down", "right": "wrong", "yes": "no"}
alias = opposites
alias["right"] = "left"
print(alias)
print(opposites) # changing the alias also changed the original
If we modify copy, opposites is unchanged:
opposites = {"up": "down", "right": "wrong", "yes": "no"}
copy = opposites.copy()
copy["right"] = "left"
print(copy)
print(opposites) # changing the copy did not change the original
Given a string, we might wish to compute a frequency table of the letters in the string — that is, how many times each letter appears.
Such a frequency table might be useful for compressing a text file. Because different letters appear with different frequencies, we can compress a file by using shorter codes for common letters and longer codes for letters that appear less frequently.
Dictionaries provide an elegant way to generate a frequency table:
letterCounts = {}
for letter in "Mississippi":
letterCounts[letter] = letterCounts.get(letter, 0) + 1
print(letterCounts)
We start with an empty dictionary. For each letter in the string, we find the current count (possibly zero) and increment it. At the end, the dictionary contains pairs of letters and their frequencies.
It might be more appealing to display the frequency table in alphabetical order. We can do that with the items and sort methods:
letterCounts = {}
for letter in "Mississippi":
letterCounts[letter] = letterCounts.get(letter, 0) + 1
letterItems = list(letterCounts.items())
letterItems.sort()
print(letterItems)
Notice in the first line we had to call the type conversion function list. That turns the promise we get from items into a list, a step that is needed before we can use the list’s sort method.