Thursday, December 28, 2023

How not to check for a key in a dictionary.

 I skim the Linkedin Python group and sometimes comment.

A few days there was a poll asking for the way to check if a key is in a Python dictionary and as I write, more than half of the 900+ respondents have chosen dict.get rather than key in dict, which is the correct answer!

When pushed, it seems they are relying on dict.get returning None if the key is not in the dict, but fail to see tha if the key being tested is in the dict, but has a value of None then their test fails. 

Here's some code:

# %%
mydict1 = {'a': 1, 'b': 2}
print(mydict1.get('c'))  # -> None

# %%

def key_in_dict1(key_, dict_: dict) -> bool:
    "FAULTY IMPLEMENTATION"
    return dict_.get(key_) != None

# This works
mydict1 = {'a': 1, 'b': 2}
print(key_in_dict1('a', mydict1))  # -> True
print(key_in_dict1('x', mydict1))  # -> False
# %%

# But adding a key of x with value None gives
# the wrong result
mydict2 = {'a': 1, 'b': 2, 'x': None}
print(key_in_dict1('x', mydict2))  # -> False

# %%

# The correct way is to use 'in'
def key_in_dict2(key_, dict_: dict) -> bool:
    "Pythonic IMPLEMENTATION"
    return key_ in dict_

# Tests:
print(key_in_dict2('a', mydict1))  # -> True
print(key_in_dict2('x', mydict1))  # -> False

# %%

# And now for keys with None as a value:
print(key_in_dict2('x', mydict2))  # -> True


Ugly, code-golf solution using dict.get()

You can alter the default value returned from dict.get if a key is not found. This allows one to use two calls of dict.get with different defaults  to be used to give a correct solution:

def key_in_dict3(key_, dict_: dict) -> bool:
    "CODE-GOLF IMPLEMENTATION USING GETs"
    default1 = None
    default2 = ""
    return default1 != default2 \
        and ( dict_.get(key_, default1) != default1
             or dict_.get(key_, default2) != default2)

# Tests:
print(key_in_dict3('a', mydict1))  # -> True
print(key_in_dict3('x', mydict1))  # -> False
print(key_in_dict3('x', mydict2))  # -> True



Of course, don't use  key_in_dict3, use version 2, i.e. key in dict.

END.