Search code examples
pythonpython-3.xclassdictionarykeyerror

Dictionary raises KeyError on lookup even though key is actually set


When I attempt to print a dictionary (or its __dict__), a KeyError is raised. The dict is a mapping from MyClass instances to floats and ints. It would be too lengthy to share the source code of MyClass.

d = dict()
d[MyClass()] = 10.023
d[MyClass()] = 1.023
d[MyClass()] = 8
print(d)
Out[16]: ---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
c:\users\[...]\lib\site-packages\IPython\core\formatters.py in __call__(self, obj)
    700                 type_pprinters=self.type_printers,
    701                 deferred_pprinters=self.deferred_printers)
--> 702             printer.pretty(obj)
    703             printer.flush()
    704             return stream.getvalue()

[...]

    618             p.pretty(key)
    619             p.text(': ')
--> 620             p.pretty(obj[key])
    621         p.end_group(step, end)
    622     return inner

KeyError: <MyClass.subclass instance>

Also the following raises KeyError:

for key in d:
    print(d[key])

Although some of the values are printed correctly before an error is raised. It seems to have trouble looking up some specific keys.

I think this is pretty weird too:

In [19]: [a in d for a in d]
Out[19]: [False, True, False, True, False]

Finally:

list(d) # works fine, just like d.keys() and d.values()
Out[19]: [<MyClass.subclass instance>,<MyClass.subclass instance>,<MyClass.subclass instance>,...]
d[list(d)[0]] # works fine
Out[20]: 10.023
d[list(d)[1]] # raises KeyError
list(d)[1] in d # returns False
list(d)[0] in d # returns True

I checked: - all keys have different hashes - list(d) contains no duplicates - list(d) instances which are correctly looked up have no noticeable difference (some are even instances of the same class) - List item

I cannot think of an explanation to this behaviour. It only manifested today. I recently made some changes in the file structure of the module containing MyClass. Before: MyClass subclasses in the same file as MyClass definition. After: MyClass subclasses in individual files in the same directory. I cannot see how this could affect any of that, but who knows.


Solution

  • Under normal circumstances this should not happen.

    However you can violate the dictionary contract that the keys must be immutable with respect to equality and hash. Because if the hash changes the objects cannot be found anymore.

    Mapping Types — dict

    [...]

    A dictionary’s keys are almost arbitrary values. Values that are not hashable, that is, values containing lists, dictionaries or other mutable types (that are compared by value rather than by object identity) may not be used as keys.

    To demonstrate what can happen when you use mutable objects with a hash-function that depends on values: Consider this class:

    class Test:
        def __init__(self, value):
            self.value = value
    
        def __hash__(self):
            return self.value
    

    And this little snippet:

    >>> t1 = Test(1)
    >>> d = {}
    >>> d[t1] = 10
    >>> t1.value = 2  # t1's hash also changes at this point
    >>> d[t1]
    KeyError: <__main__.Test object at 0x0000020FCA5AD748>
    

    This is of course overly simplified but I suspect that something like this happens in your code. I propose you monitor changes to the values that are relevant for __hash__ and __eq__ during the program execution.