5.6. Pythonic Ways to Use Dictionaries¶
Dictionaries are at the core of many Python programs because of the flex- ibility that key-value pairs (discussed further in Chapter 7) provide by map- ping one piece of data to another. Therefore, it’s useful to learn about some of the dictionary idioms Python code commonly uses.
For further information about dictionaries, consult Python program- mer Brandon Rhodes’s incredible talks about dictionaries and how they work: “The Mighty Dictionary” at PyCon 2010, viewable at https://invpy.com/ mightydictionary, and “The Dictionary Even Mightier” at PyCon 2017, view- able at https://invpy.com/dictionaryevenmightier. ## Use get() and setdefault() with Dictionaries Trying to access a dictionary key that doesn’t exist will result in a KeyError error, so programmers will often write unpythonic code to avoid the situa- tion, like this:
>>> # Unpythonic
>>>
>>> numberOfPets= {'dogs': 2}
>>> if 'cats' in numberOfPets: # Check if 'cats' exists as a key.
>>> print('I have', numberOfPets['cats'], 'cats.')
>>> else:
>>> print('I have 0 cats.')
I have 0 cats.
This code checks whether the string ‘cats’ exists as a key in the numberOfPets dictionary. If it does, a print() call accesses numberOfPets[‘cats’] as part of a message for the user. If it doesn’t, another print() call prints a string without accessing numberOfPets[‘cats’] so it doesn’t raise a KeyError . This pattern happens so often that dictionaries have a get() method that allows you to specify a default value to return when a key doesn’t exist in the dictionary. The following pythonic code is equivalent to the previous example:
>>> # Pythonic Example
>>> numberOfPets = {'dogs': 2}
>>> print('I have', numberOfPets.get('cats', 0), 'cats.')
I have 0 cats.
The numberOfPets.get(‘cats’, 0) call checks whether the key ‘cats’ exists in the numberOfPets dictionary. If it does, the method call returns the value for the ‘cats’ key. If it doesn’t, it returns the second argument, 0 , instead. Using the get() method to specify a default value to use for nonexistent keys is shorter and more readable than using if - else statements.
Conversely, you might want to set a default value if a key doesn’t exist. For example, if the dictionary in numberOfPets doesn’t have a ‘cats’ key, the instruction numberOfPets[‘cats’] += 10 would result in a KeyError error. You might want to add code that checks for the key’s absence and sets a default value:
>>> # Unpythonic Example
>>> numberOfPets = {'dogs': 2}
>>> if 'cats' not in numberOfPets:
>>> numberOfPets['cats'] = 0
>>> numberOfPets['cats'] += 10
>>> numberOfPets['cats']
10
But because this pattern is also common, dictionaries have a more pythonic setdefault() method. The following code is equivalent to the previ- ous example:
>>> # Pythonic Example
>>> numberOfPets = {'dogs': 2}
>>> numberOfPets.setdefault('cats', 0) # Does nothing if 'cats' exists.
0
>>> numberOfPets['cats'] += 10
>>> numberOfPets['cats']
10
If you’re writing if statements that check whether a key exists in a dictionary and sets a default value if the key is absent, use the setdefault() method instead. ## Use collections.defaultdict for Default Values You can use the collections.defaultdict class to eliminate KeyError errors entirely. This class lets you create a default dictionary by importing the collections module and calling collections.defaultdict() , passing it a data type to use for a default value. For example, by passing int to collections .defaultdict() , you can make a dictionary-like object that uses 0 for a default value of nonexistent keys. Enter the following into the interactive shell:
>>> import collections
>>> scores = collections.defaultdict(int)
>>> scores
defaultdict(int, {})
>>> scores['Al'] += 1 # No need to set a value for the 'Al' key first.
>>> scores
defaultdict(int, {'Al': 1})
>>> scores['Zophie'] # No need to set a value for the 'Zophie' key first.
0
>>> scores['Zophie'] += 40
>>> scores
defaultdict(int, {'Al': 1, 'Zophie': 40})
Note that you’re passing the int() function, not calling it, so you omit the parentheses after int in collections.defaultdict(int) . You can also pass list to use an empty list as the default value. Enter the following into the interactive shell:
>>> import collections
>>> booksReadBy = collections.defaultdict(list)
>>> booksReadBy['Al'].append('Oryx and Crake')
>>> booksReadBy['Al'].append('American Gods')
>>> len(booksReadBy['Al'])
2
>>> len(booksReadBy['Zophie']) # The default value is an empty list.
0
If you need a default value for every possible key, it’s much easier to use collections.defaultdict() than use a regular dictionary and constantly call the setdefault() method. ## Use Dictionaries Instead of a switch Statement Languages such as Java have a switch statement, which is a kind of if - elif - else statement that runs code based on which one of many values a specific variable contains. Python doesn’t have a switch statement, so Python pro- grammers sometimes write code like the following example, which runs a different assignment statement based on which one of many values the season variable contains:
>>> # All of the following if and elif conditions have "season ==":
>>> season='Winter'
>>> if season == 'Winter':
>>> holiday = 'New Year\'s Day'
>>> elif season == 'Spring':
>>> holiday = 'May Day'
>>> elif season == 'Summer':
>>> holiday = 'Juneteenth'
>>> elif season == 'Fall':
>>> holiday = 'Halloween'
>>> else:
>>> holiday = 'Personal day off'
This code isn’t necessarily unpythonic, but it’s a bit verbose. By default, Java switch statements have “fall-through” that requires each block to end with a break statement. Otherwise, the execution continues on to the next block. Forgetting to add this break statement is a common source of bugs. But all the if - elif statements in our Python example can be repetitive. Some Python programmers prefer to set up a dictionary value instead of using if-elif statements. The following concise and pythonic code is equiv- alent to the previous example:
>>> holiday = {'Winter':'New Year\'s Day',
>>> 'Spring':'May Day',
>>> 'Summer':'Juneteenth',
>>> 'Fall':'Halloween'
>>> }.get(season, 'Personal day off')
This code is just a single assignment statement. The value stored in holiday is the return value of the get() method call, which returns the value for the key that season is set to. If the season key doesn’t exist, get() returns ‘Personal day off’ . Using a dictionary will result in more concise code, but it can also make your code harder to read. It’s up to you whether or not to use this convention.