Checking Python Dict Keys Is Simple. Right?
This program doesn't work:
- favorites = {
- "appetizer": "calamari",
- "vegetable": "broccoli",
- "beverage": "coffee",
- }
-
- def describe(category):
- print("My favorite {} is {}.".format(
- category, favorites[category]))
-
- categories = ['dessert', 'beverage', 'vegetable', 'soup']
- for category in categories:
- describe(category)
Run, and it blows up on you:
Traceback (most recent call last): File "favsimple.py", line 13, indescribe(category) File "favsimple.py", line 9, in describe category, favorites[category])) KeyError: 'dessert'
Broadly, you have three ways to fix this problem. First is the "key in dict" idiom:
- def describe(category):
- if category in favorites:
- message = "My favorite {} is {}.".format(
- category, favorites[category])
- else:
- message = "I don't have a favorite {}. I love them all!".format(category)
- print(message)
That works. Here's the output:
I don't have a favorite dessert. I love them all! My favorite beverage is coffee. My favorite vegetable is broccoli. I don't have a favorite soup. I love them all!
Now imagine you can't modify describe()
in the first
place. Maybe other code relies on its original behavior, and you don't
want to refactor everything. Or maybe you import it from a library, so
modifying describe()
isn't even an option.
You can still
fix the program by catching KeyError
:
- def describe(category):
- print("My favorite {} is {}.".format(
- category, favorites[category]))
-
- categories = ['dessert', 'beverage', 'vegetable', 'soup']
- for category in categories:
- try:
- describe(category)
- except KeyError:
- print("I don't have a favorite {}. I love them all!".format(category))
Same output. Use try/except KeyError
when you don't
have direct access to the dictionary in your code, or getting direct
access is a pile of trouble.
But let's focus on working with the dict directly, because
frequently you can. That brings us to the third approach: the
dict.get()
method. This fetches the value if the key is
present, or an alternate value if not - all without raising any
exception. You can call it with two arguments - the key, and the
default value:
- >>> # Some stock prices.
- ... stocks = {"GNC": 8.63, "BRK.B": 170.22, "ADNT": 66.48,
- ... "STC": 44.82, "GK": 94.45}
- >>> print(stocks.get("GNC", 0.0))
- 8.63
- >>> print(stocks.get("SSL", 0.0))
- 0.0
You can also call it with one argument, which makes the
default None
.
- >>> print(stocks.get("GNC"))
- 8.63
- >>> print(stocks.get("SSL"))
- None
You might use get
in a function like this:
- # Atomic numbers of some elements.
- nobles = {"He": 2, "Ne": 10,
- "Ar": 18, "Kr": 36, "Xe": 54}
-
- def showelement(symbol):
- num = nobles.get(symbol, "unknown")
- print("The atomic number of {} is {}".format(
- symbol, num))
So showelement("Kr")
prints "The atomic number of Kr is
36", and showelement("Rn")
prints "The atomic number of
Rn is unknown". dict.get()
is perfect here.
Can we use it in describe()
? Yes, but that is trickier
than it seems, because - unlike in showelement()
- the
output format is completely different if the key is not present.
Here's one approach:
- # First version...
- def describe(category):
- fav = favorites.get(category) # default here is None
- if fav is None:
- message = "I don't have a favorite {}. I love them all!".format(category)
- else:
- message = "My favorite {} is {}.".format(category, fav)
- print(message)
This works... usually. The idea is to rely on the default value to
signal when the key is absent. But what if the key is present,
and its value is the same as the default? None
can be a value in a dictionary, after all. Even if we think this
dictionary won't have it, what if some bug inserts it anyway? The code
above would mask that bug, lurking in your program like a land
mine.
What we need is an out-of-band sentinel value - in other words,
something which could not possibly be a dict value. The best way is to
create a unique object - literally, an instance of
object
- and pass that as the default to .get()
:
- # Better version.
- def describe(category):
- # Sentinel signaling a missing key.
- missing = object()
- fav = favorites.get(category, missing)
- if fav is missing:
- message = "I don't have a favorite {}. I love them all!".format(category)
- else:
- message = "My favorite {} is {}.".format(category, fav)
- print(message)
missing
cannot possibly be the value of any key,
because we created missing
just before looking in the
dictionary. So if fav
is missing
, we know
for sure it's not in the dictionary.
These three approaches - "key in dict", "try/except KeyError", and "dict.get()" - have different benefits. Practice each, so you are familiar enough to use the best for different situations.
(And, all the above is for single-threaded code. If your program has multiple threads, you have other surprises to think about. We'll cover that in another article.)