Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

unexpected behaviour of dictionary membership check

An unhashable object cannot be inserted into a dict. It is documented, there is good reason for it.

However, I do not understand why it affects a membership test:

if value not in somedict:
    print("not present")

I was assuming that a membership test can return only True or False. But when the value is unhashable, it fails with TypeError: unhashable type. I would say True should be the correct answer of this not in test, because the value is clearly not contained in somedict and it is fully irrelevant that it cannot be inserted.

Another example:

try:
    result = somedict[value]
except KeyError:
    # handle the missing key

When the value is unhashable, it fails with TypeError: unhashable type I would expect the KeyError instead.

Also:

somedict.get(value, default)

does not return the default, but throws the TypeError.


So why unhashable in somedict does not evaluate to False and what is the correct test returning just True or False?


Update:

object.__contains__(self, item)

Called to implement membership test operators. Should return true if item is in self, false otherwise.

(from "Data Model - Python Documentation")


Appendix:

This is a simplified part of a user interface program, which failed when one of the arguments was a dict.

# args = list of function arguments created from user's input
# where "V1" stands for value1 and "V2" stands for value2
XLAT = {'V1': value1, 'V2': value2} 
args = [XLAT.get(a, a) for a in args]
function(*args)
like image 759
VPfB Avatar asked Jan 25 '18 13:01

VPfB


People also ask

Which keys are allowed in dictionaries?

Second, a dictionary key must be of a type that is immutable. For example, you can use an integer, float, string, or Boolean as a dictionary key. However, neither a list nor another dictionary can serve as a dictionary key, because lists and dictionaries are mutable.

What is the property of dictionary?

Properties of Dictionary KeysThe values in the dictionary can be of any type, while the keys must be immutable like numbers, tuples, or strings. Dictionary keys are case sensitive- Same key name but with the different cases are treated as different keys in Python dictionaries.

What Do you Mean by dictionary in Python mention any two Features of a dictionary data type?

Python's dictionaries are kind of hash table type. They work like associative arrays or hashes found in Perl and consist of key-value pairs. A dictionary key can be almost any Python type, but are usually numbers or strings. Values, on the other hand, can be any arbitrary Python object.

Why are dictionaries important in Python?

Python dictionaries allow us to associate a value to a unique key, and then to quickly access this value. It's a good idea to use them whenever we want to find (lookup for) a certain Python object. We can also use lists for this scope, but they are much slower than dictionaries.


1 Answers

The reason is that the test for a potential key being part of a dictionary is done by generating the hash value of the potential key. If the potential key cannot provide a hash value (if the object is not hashable), the test cannot take place.

You are right, in a way, that in this case the existence test could just say "no, not present" ("because it cannot be inserted anyway").

This is not done this way because it could cloak a lot of programming errors.

If you program cleanly, you will most likely never check for an unhashable object whether it is in a dictionary or not. I. e. it requires quite an amount of fantasy to come up with a case where you actually would. (I wouldn't say it is completely out of the question, though.) The amount of cases where such a check is only happening because a programming error leads to a situation where you do something accidentally, is way larger. So the exception indicates that you should look at the spot in the code.

If you know what you are doing (maybe the case in your situation), you should simply catch that error:

try:
    if strange_maybe_unhashable_value in my_dict:
        print("Yes, it's in!")
    else:
        print("No, it's not in!")
except TypeError:
    print("No, it's not even hashable!")

If you want to combine that with your KeyError handling:

try:
    result = somedict[value]
except (KeyError, TypeError):
    # handle the missing key

or

try:
    result = somedict[value]
except KeyError:
    # handle the missing key
except TypeError:
    # react on the thing being unhashable

To provide another aspect which is rather esoteric:

An object might be hashable at some time and unhashable at another (maybe later). This should of course never be the case but can happen, e. g. if the hash value relies on something external. Despite common assumptions, being hashable is independent from being immutable (although one often relies on the other). So an object could change while being part of a dictionary, and this could change its hash value. While this is an error in itself and will lead to having the dictionary not working properly, this might also be a design-reason not to simply say "not present" or raise a KeyError but raise the TypeError instead.

like image 97
Alfe Avatar answered Sep 19 '22 10:09

Alfe