3

So, I've done a lot of research on this and saw various other links and conferred with the Python documentation, however, I'm still a bit unclear on this.

Maybe the way I see classes in Python is a bit wrong.

As I understand, keys in a dictionary must be immutable. However, classes can be keys in a dictionary because of their default hash implementation (I think?). Why is this the case since classes are mutable?

For example,

class C:
    def __init__(self):
        self.val = 15
        self.array = []

c = C()

D = {c: 15}
c.val = 14
c.array.append(15)

print(D[c])

Why is this okay?

John
  • 39
  • 3
  • "Why doesn't changing things in your class change the hash?" because the output of the hash() function isn't dependent on the contents of the attributes by default (unless you explicitly override its implementation to be). My answer is correct but the part about the hash(c) == id(c) is incorrect, and I'm trying to understand why – cs95 Apr 27 '23 at 20:18
  • Does this answer your question? [What is the default \_\_hash\_\_ in python?](https://stackoverflow.com/questions/11324271/what-is-the-default-hash-in-python) – Guru Stron Apr 27 '23 at 20:20
  • Not sure about that, but I think the generated hash for an object is related to its class and/or memory metadata, not its attributes. So since this metadata wouldn't change, the hash is the same – L.Stefan Apr 27 '23 at 20:23
  • [The requirement for keys in the dictionary is hashable](https://docs.python.org/3/library/stdtypes.html?highlight=dict#mapping-types-dict), not immutable. In fact, even a mutable object such as `list` can be contained in a dictionary by subclassing it and defining a `__hash__` method. Furthermore, you can change the hash value after you have put it in the dictionary. Of course, this is bad practice. – ken Apr 27 '23 at 20:37
  • You're not using classes as keys there. Just an *instance*. – Kelly Bundy Apr 27 '23 at 20:41

1 Answers1

5

Instances of your C class are actually hashable, it comes with a default implementation of __hash__ which pertains to the identity of that object:

>>> hash(c) # calls c.__hash__()
306169194

This __hash__ implementation allows your instance to be used as a key in a dictionary.

This explains "Why doesn't changing things in your class change the hash?" — because the identity/reference of the instance doesn't change even if its contents do.

On older versions of python, this used to be exactly equal to the object's id, but from python 3 onwards, it seems to be some derivative of it. This post goes into the gory details on the hash implementation.


Now let's say you wanted to prevent instances of your class from being used as a key... you could then do this (from the documentation):

If a class that does not override __eq__() wishes to suppress hash support, it should include __hash__ = None in the class definition.

class C:
    def __init__(self):
        self.val = 15
        self.array = []

    __hash__ = None # suppressing hash support

c = C()

And now you get a familiar sounding TypeError if you attempt to retrieve the hash of c:

>>> hash(c)
# TypeError: unhashable type: 'C'

Naturally, this also implies you cannot use c as a dictionary key anymore (IOW trying to initialize {c: 15} would also throw the same TypeError since c is not hashable).

Kelly Bundy
  • 23,480
  • 7
  • 29
  • 65
cs95
  • 379,657
  • 97
  • 704
  • 746
  • Why doesn't changing things in your class change the hash? – John Apr 27 '23 at 20:17
  • 2
    @John answered under your question but to summarize, the default implementation of `hash()` is to return a static number that has to do with the object's reference/identity, independent of its contents. – cs95 Apr 27 '23 at 20:23
  • @John @KellyBundy ooh thanks for pointing that out, that actually makes the error message a lot more pertinent `TypeError: unhashable type: 'C'` – cs95 Apr 27 '23 at 21:02