How to fix dictionary access bugs in Python

In this post I am going to discuss how accessing a value in a dict can easily introduce a bug in your code and how to fix it.

Access

One of the most common scenarios with dictionaries is accessing a value.

<span>value</span> <span>=</span> <span>my_dict</span><span>[</span><span>key</span><span>]</span>
<span>value</span> <span>=</span> <span>my_dict</span><span>[</span><span>key</span><span>]</span>
value = my_dict[key]

Enter fullscreen mode Exit fullscreen mode

Problem: If key doesn’t exist in my_dict, the code will raise a KeyError

Solution: To avoid this scenario you have three options:

  1. Check if key exists before accessing

    <span>if</span> <span>key</span> <span>in</span> <span>my_dict</span><span>:</span>
    <span>value</span> <span>=</span> <span>my_dict</span><span>[</span><span>key</span><span>]</span>
        <span>if</span> <span>key</span> <span>in</span> <span>my_dict</span><span>:</span>
           <span>value</span> <span>=</span> <span>my_dict</span><span>[</span><span>key</span><span>]</span>
    if key in my_dict: value = my_dict[key]
  2. Wrap the access in try-except block

    <span>try</span><span>:</span>
    <span>value</span> <span>=</span> <span>my_dict</span><span>[</span><span>key</span><span>]</span>
    <span>except</span> <span>KeyError</span><span>:</span>
    <span>...</span>
        <span>try</span><span>:</span>
            <span>value</span> <span>=</span> <span>my_dict</span><span>[</span><span>key</span><span>]</span>
        <span>except</span> <span>KeyError</span><span>:</span>
            <span>...</span>
    try: value = my_dict[key] except KeyError: ...
  3. Use Python’s get function to avoid checking for key in dict

    <span>value</span> <span>=</span> <span>my_dict</span><span>.</span><span>get</span><span>(</span><span>key</span><span>)</span>
        <span>value</span> <span>=</span> <span>my_dict</span><span>.</span><span>get</span><span>(</span><span>key</span><span>)</span>
    value = my_dict.get(key)

Since, get option (3) looks the neatest, I tend to use that the most.

Now, suppose for a function I am writing, I am accessing a value from dict but I only want to proceed if the given key exists in the dict.

I would write something like this:

<span>value</span> <span>=</span> <span>my_dict</span><span>.</span><span>get</span><span>(</span><span>key</span><span>)</span>
<span>if</span> <span>not</span> <span>value</span><span>:</span>
<span>raise</span> <span>MyError</span><span>(</span><span>'Found bad key'</span><span>)</span>
<span># continue with code </span>
<span>value</span> <span>=</span> <span>my_dict</span><span>.</span><span>get</span><span>(</span><span>key</span><span>)</span>
<span>if</span> <span>not</span> <span>value</span><span>:</span>
    <span>raise</span> <span>MyError</span><span>(</span><span>'Found bad key'</span><span>)</span>

<span># continue with code </span>
value = my_dict.get(key) if not value: raise MyError('Found bad key') # continue with code

Enter fullscreen mode Exit fullscreen mode

At first glance, the code looks fine, but I have introduced a bug in it.

Let me illustrate with an example:

<span># Assume my_dict as: </span><span>my_dict</span> <span>=</span> <span>{</span>
<span>'key1'</span><span>:</span> <span>'value1'</span><span>,</span>
<span>'key3'</span><span>:</span> <span>[],</span>
<span>}</span>
<span># Assume my_dict as: </span><span>my_dict</span> <span>=</span> <span>{</span>
    <span>'key1'</span><span>:</span> <span>'value1'</span><span>,</span>
    <span>'key3'</span><span>:</span> <span>[],</span>
<span>}</span>
# Assume my_dict as: my_dict = { 'key1': 'value1', 'key3': [], }

Enter fullscreen mode Exit fullscreen mode

Key Behavior Expected?
key1 Regular code execution Yes
key2 raises MyError Yes
key3 raises MyError No
  • key2 raises the exception since get function will return None by default for non-existing keys.

  • key3 exists in the dict however my code will still throw an error because if not [] is also True, leading to unintentional behavior.

Quick Bad Fix

Reading points about key2 and key3 from above, modifying the validation condition seems like the most obvious fix:

<span>value</span> <span>=</span> <span>my_dict</span><span>.</span><span>get</span><span>(</span><span>key</span><span>)</span>
<span>if</span> <span>value</span> <span>is</span> <span>None</span><span>:</span>
<span>raise</span> <span>MyError</span><span>(</span><span>'Found bad key'</span><span>)</span>
<span>value</span> <span>=</span> <span>my_dict</span><span>.</span><span>get</span><span>(</span><span>key</span><span>)</span>
<span>if</span> <span>value</span> <span>is</span> <span>None</span><span>:</span>
    <span>raise</span> <span>MyError</span><span>(</span><span>'Found bad key'</span><span>)</span>
value = my_dict.get(key) if value is None: raise MyError('Found bad key')

Enter fullscreen mode Exit fullscreen mode

This, however, only fixes one symptom/effect of the bug that is when the value is an empty data type like '' or []. If the value in my_dict is None the above code will still raise the error.

Fix

Before writing a fix for your code, it is usually a good idea to think about the root cause of the bug, rather than the symptoms that are showing up. In our case, it was not the if condition that was causing the bug – it was the get function.

I only want to proceed if the given key exists in the dict

To encode the described behavior the correct code block would look something like this:

<span>if</span> <span>key</span> <span>in</span> <span>my_dict</span><span>:</span>
<span>value</span> <span>=</span> <span>my_dict</span><span>[</span><span>key</span><span>]</span>
<span># rest of code </span><span>else</span><span>:</span>
<span>raise</span> <span>MyError</span><span>(</span><span>'Found bad key'</span><span>)</span>
<span>if</span> <span>key</span> <span>in</span> <span>my_dict</span><span>:</span>
    <span>value</span> <span>=</span> <span>my_dict</span><span>[</span><span>key</span><span>]</span>
    <span># rest of code </span><span>else</span><span>:</span>
    <span>raise</span> <span>MyError</span><span>(</span><span>'Found bad key'</span><span>)</span> 
if key in my_dict: value = my_dict[key] # rest of code else: raise MyError('Found bad key')

Enter fullscreen mode Exit fullscreen mode

Learning

  • Capturing intent is critical in writing robust code.
  • First step to fixing bugs should be to list assumptions and work from there.

原文链接:How to fix dictionary access bugs in Python

© 版权声明
THE END
喜欢就支持一下吧
点赞5 分享
pencil and a dream can take you anywhere.
拿起笔,写下你的梦想,你的人生就从此刻起航
评论 抢沙发

请登录后发表评论

    暂无评论内容