Search code examples
pythonhashdata-class

How do I make a python dataclass inherit __hash__?


The following will work, but I'd rather not need to repeat the __hash__ in each subclass. Is there a way to tell the dataclass to inherit the hash function (i.e. not set it to None)?

from dataclasses import dataclass


@dataclass
class Hashable:

    def __hash__(self):
        hashed = hash((
            getattr(self, key)
            for key in self.__annotations__
            ))
        return hashed


@dataclass
class Node(Hashable):
    name: str = 'Undefined'

    def __hash__(self):
        return Hashable.__hash__(self)

Solution

  • The reason your __hash__ is being set to None is that dataclasses is trying to stop you from shooting yourself in the foot. Your second class has eq=True for the dataclass decorator (this is the default value). From the docs:

    Here are the rules governing implicit creation of a __hash__() method. Note that you cannot both have an explicit __hash__() method in your dataclass and set unsafe_hash=True; this will result in a TypeError.

    If eq and frozen are both true, by default dataclass() will generate a __hash__() method for you. If eq is true and frozen is false, __hash__() will be set to None, marking it unhashable (which it is, since it is mutable). If eq is false, __hash__() will be left untouched meaning the __hash__() method of the superclass will be used (if the superclass is object, this means it will fall back to id-based hashing).

    So just pass eq=False:

    In [1]: from dataclasses import dataclass
       ...:
       ...:
       ...: @dataclass
       ...: class Hashable:
       ...:
       ...:     def __hash__(self):
       ...:         hashed = hash((
       ...:             getattr(self, key)
       ...:             for key in self.__annotations__
       ...:             ))
       ...:         return hashed
       ...:
       ...:
       ...: @dataclass(eq=False)
       ...: class Node(Hashable):
       ...:     name: str = 'Undefined'
       ...:
    
    In [2]: hash(Node())
    Out[2]: -9223372036579626267
    

    However, as pointed out in the comments, this isn't very safe, since you have a mutable object that is now hash-able, and inconsistently so with it's implementation of __eq__, which it is inheriting from Hashable