7. Dictionaries

7.1. Creating and using dictionaries

We can do a lot of things in code using what we know so far. Some of the things we can do in code is kind of, well, “clunky.” Consider the following example. Suppose we want to keep track of a group of students and their major field of study. The only way we know how to do that currently is with lists.

names = ["Shayla Succotash", "Amy Avocado", "Larry Linguini", "Kevin Kumquat"]
majors = ["Comp Sci", "Accounting", "Math", "Comp Sci"]

We are using lists to store corresponding attributes of each student. Each student has a unique index for their attributes. Suppose we ran the code that follows.

print(names[1])
print(majors[1])
print()
print(names[3])
print(majors[3])

The resulting output of this code is:

Amy Avocado
Accounting

Kevin Kumquat
Comp Sci

This is all fine, but it’s also a bit awkward. If we want to know a student’s major, we have to find their location in the first list, and then use their index to look up their major in the second list, like this:

x = "Kevin Kumquat"
found = False

for i in range(0, len(names)):
    if names[i] == x:
        print(majors[i])

if not found:
    print("Couldn't find a major for %s." % x)

Yuck. What we really want to do is something more simple, like this:

x = "Kevin Kumquat"
m = get_major(x)
# m is now "Comp Sci"

We could easily define a function like get_major, but fortunately we don’t have to.

To make this easier, we will forgo lists (for now) and use something called a dictionary. At first glance, a dictionary is just like an English dictionary. With an English dictionary, we look up a word (which is a string) and next to the word is its definition (also a string). Python dictionaries can be used on more than just strings, but we’ll focus on strings for now. We can apply this concept of an English dictionary to, say, look up "Amy Avocado" and receive Accounting.

Let’s create a dictionary to associate names with majors. Here is how we do it (see Listing 7.1).

Listing 7.1 Defining a dictionary (dict)
majors = {
    "Shayla Succotash" : "Comp Sci",
    "Amy Avocado" : "Accounting",
    "Larry Linguini" : "Math",
    "Kevin Kumquat" : "Comp Sci",
}

Now, we can look up majors by typing this:

print(majors["Shayla Succotash"])
print(majors["Larry Linguini"])

This produces:

Comp Sci
Math

Notice how we create (i.e., define) a dictionary. We use curly braces to denote the dictionary type (which is called dict kind of like other types such as str and int). We use colons (:) to separate the name from the major. Also, notice how we use dictionaries to look up values. We use square brackets, which looks a lot like how we used lists.

Terminology time! The values on the left side of the : in our dictionary definition (e.g., "Shayla Succotash", etc.) are called keys. The values on the right (e.g., "Comp Sci", etc.) are simply called values. Each entry in the dictionary that associates a name with a major is called a key-value pair. "Larry Linguini" and "Math" is an example of a key-value pair.

Okay, so now what? Well, in this book we often ask questions that start with “What happens if…” or “What happens when…?” to enhance our understanding of a topic. Here’s one: What happens if we try to look up something that doesn’t exist in the dictionary? Can we create an example of this in code? You try it without looking at the next paragraph.

Were you able to come up with an example? If so, did it look like Listing 7.2?

Listing 7.2 Attempting to access a key that does not exist
print(majors["Brooklyn Broccolini"])

Clearly, there is no key for “Brooklyn Broccolini” defined in majors in Listing 7.1. What happens when we run the Listing 7.2 code?

Did you try it? What did you find out?

If we try to specify a key that does not exist in the dictionary, the code produces a KeyError. We could handle KeyError if we so chose, like the code example found in Listing 7.3.

Listing 7.3 Handling KeyError
try:
    student = "Brooklyn Broccolini"
    print(majors[student])
except KeyError:
    print("There is no major listed for %s." % student)

If we didn’t want to rely on handling a KeyError, we could check first to see if a certain key exists in a dictionary (see Listing 7.4).

Listing 7.4 Checking for the existence of a key first
1if "Brooklyn Broccolini" in majors:
2    print(majors["Brooklyn Broccolini"])
3else:
4    print("No major listed for that student.")

Even though line 2 could technically raise a KeyError, the if statement in line 1 checks first to make sure line 2 won’t raise a KeyError.

To see a different example, let’s return to the idea of a Python dictionary as being similar to an English dictionary. In an English dictionary, we can look up a word and find its definition. The word is a key. The definition is the word’s value.

We will now write a program that creates a very small English dictionary that allows the user to look up definitions of words (see Listing 7.5).

Listing 7.5 Using a dict to create an English dictionary
 1d = {
 2    "alpaca" : "A domesticated camelid",
 3    "bow" : "A weapon that shoots arrows",
 4    "rose" : "A prickly bush that produces fragrant flowers",
 5}
 6
 7word = input("Enter a word to lookup (enter nothing to quit): ")
 8while word != "":
 9    if word in d:
10        print("Definition:", d[word])
11    else:
12        print("There is no definition for '%s'" % word)
13
14    word = input("Enter a word to lookup (enter nothing to quit): ")

This is lovely, but English dictionaries often have more than one definition for a word. In Listing 7.5, the key "rose" is a homophone. The word “rose” could also mean “the past tense of rise.”

It appears that we want a key to have multiple values, potentially. If we wanted to store multiple values in the past, we have relied on using a list. So, we shall use a list once again. Let us modify the definition of d in Listing 7.5 so that the values are no longer strings, but rather lists of strings (that is, a list of str).

d = {
    "alpaca" : ["A domesticated camelid"],
    "bow" : ["A weapon that shoots arrows",
             "To bend at the waist",
             ],
    "rose" : ["A prickly bush that produces fragrant flowers",
              "The past tense of rise",
              ],
}

The expression d["rose"] now yields a list of two items: "A prickly bush ..." and "The past ...". Because the expression d["rose"] produces a list, I can do list-things to it. For example, the expression len(d["rose"]) produces the int value 2, and the expression d["rose"][1] produces the str value "The past tense of rise".

When we look up a word the user enters, we can use a for loop to show each of the definitions. See how we have modified Listing 7.5 to create Listing 7.6.

Listing 7.6 Using a dict to print word meanings
 1d = {
 2    "alpaca" : ["A domesticated camelid"],
 3    "bow" : ["A weapon that shoots arrows",
 4             "To bend at the waist",
 5             ],
 6    "rose" : ["A prickly bush that produces fragrant flowers",
 7              "The past tense of rise",
 8              ],
 9}
10
11word = input("Enter a word to lookup (enter nothing to quit): ")
12while word != "":
13    if word in d:
14        for defn in d[word]:
15            print("Definition:", defn)
16    else:
17        print("There is no definition for '%s'" % word)
18
19    word = input("Enter a word to lookup (enter nothing to quit): ")

There’s an interesting tidbit in Listing 7.6 you may have missed. Note lines 14 and 15. My initial choice for a variable name was def. However, def is a reserved keyword in Python, which we know is used to define functions. So, I had to chose another variable name. Instead, I chose defn to stand for “definition.”

What if we want to remove a key-value pair from a dictionary? We can use the del keyword to accomplish this. Consider Listing 7.7, specifically line 13.

Listing 7.7 Deleting a key-value pair from a dict
 1d = {
 2    "alpaca" : ["A domesticated camelid"],
 3    "bow" : ["A weapon that shoots arrows",
 4             "To bend at the waist",
 5             ],
 6    "rose" : ["A prickly bush that produces fragrant flowers",
 7              "The past tense of rise",
 8              ],
 9}
10
11word = input("Enter a word to remove from the dictionary: ")
12if word in d:
13    del d[word]
14else:
15    print("Sorry, %s doesn't seem to be defined in our dictionary." % word)

If the user were to type alpaca, the pair for the key "alpaca" would be removed. Only the pairs for the keys "bow" and "rose" would remain. (Readers will note that removing alpacas from the dictionary would be a terrible calamity because they are adorable and charming. Llamas, on the other hand, are jerks.)

7.2. Iterating through dictionaries

There may be instances where we wish to iterate through a dict similar to how we might traverse a list. Suppose have the following dictionary definition.

calories = {
    "egg" : 80.0,
    "milk" : 80.0,
    "cheerios" : 100.0,
    "blueberry" : 0.78,
    "strawberry" : 4.0,
}

calories is a dict that acts as a food database. It allows programmers to look up the calories for a given food. Thus, calories["strawberry"] gives us 4.0. Suppose we want to see all the foods in our database. We might write the following code.

for food in calories:
    print(food)

When a for loop operates on a dictionary, the value assigned to the variable food is actually the key. The output of this code is each of the keys (the food names), one on each line.

Now, run this code. Run it several times. What do you notice? In all likelihood, the ordering changes for the food names. We cannot rely on the keys to be kept in order when we use a dictionary. If we want the keys to be sorted alphabetically, we would need to do something like what we see in Listing 7.8.

Listing 7.8 Iterating through keys of a dict
1for food in sorted(calories.keys()):
2    print(food)

keys() creates a list of the keys in calories. sorted then returns a new list with the keys in alphabetical order.

If you’re curious why the keys are ordered differently when you iterate through them using a loop, you’ll learn more when you take a class on data structures and learn about hash tables. Hash tables are used to make dictionaries. (Very curious readers should look on YouTube for a video from PyCon 2010 titled “Mighty Dictionary.” This will give you an idea how hash tables work and how they organize the internals of a dictionary.)

7.3. Exercises

  1. Given the following dict definition

    d = {"a" : 1, "b" : 2, "c" : 3}
    

    write the type and value of each of the following expressions. If there is an error, state the error instead.

    1. d

    2. d["b"]

    3. d[2]

    4. d["x"]

    5. "c" in d

    6. 3 in d

  2. Given the following dict definition

    d = {"a" : 1, "b" : 2, "c" : 3}
    

    write code that adds a new key "d" and gives it the value 4, and then modifies the key "c" and gives it the value "radish".

  3. Write a function that stores a dict to a file. The function’s name should be dict_to_file and it should have two parameters. The first parameter should be the dict. The second parameter should be the new file’s name. Assume that each key is a str and each value is also a str. The format of the file should be each key on a line followed by its value on the next line.

  4. Write a function that creates a dict and fills its contents using lines from a file. The function’s name should be file_to_dict and it should have one parameter: the name of the file. The function should return the dict it creates. Assume the file’s format consists of a key on a single line followed by the key’s value on the line after it. Also assume both the key and the value are strings.

  5. Write a function that stores a dict that maps strings to lists into a file. The function’s name should be str2list_to_file and it should have two parameters. The first parameter should be the dict. The second parameter should be the new file’s name. Assume that each key is a str and each value is a list. The format of the file should be each key on a line followed by a series of lines, where each line is an item in the list. The lines that are keys should be prefixed with the string "K:". This will tell us which lines are keys and which are values.

    For example, suppose we define

    names = { "Anderson" : ["Steve", "Tyler"], "Smith" : [Susie, Aaron] }
    

    calling str2list_to_file(names, "names.txt") should create a file named names.txt that has the contents

    K:Anderson
    Steve
    Tyler
    K:Smith
    Susie
    Aaron