# Dataclasses \`has\_explicit\_hash\` broken?

**URL:** <https://discuss.python.org/t/dataclasses-has-explicit-hash-broken/89925>\
**Category:** Python Help\
**Created:** [April 28, 2025, 7:39am UTC](https://discuss.python.org/t/dataclasses-has-explicit-hash-broken/89925 "2025-04-28T07:39:41Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![OfekShilon](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ofekshilon/32/27054_2.png) [@OfekShilon](https://discuss.python.org/u/OfekShilon)\
**Post date:** [April 28, 2025, 7:39am UTC](https://discuss.python.org/t/dataclasses-has-explicit-hash-broken/89925/1 "2025-04-28T07:39:41Z")

</div>

We’re having problems when trying to implement ` __hash__ ` for a dataclass that has successors. Investigation led to the [following lines](https://github.com/python/cpython/blob/632978f00549555f6e4391c0e24f599286471637/Lib/dataclasses.py#L1057-L1059) in dataclasses.py:

```python
    class_hash = cls. __dict__.get(' __hash__', MISSING)
    has_explicit_hash = not (class_hash is MISSING or
                             (class_hash is None and ' __eq__' in cls. __dict__ ))

```

It seems dataclasses decides whether a class has an explicit hash by examining only the immediate class, and not its mro. This is at the very least inconsistent, as in other places dataclasses.py does examine the mro to reach similar decisions (eg, [slots](https://github.com/python/cpython/blob/632978f00549555f6e4391c0e24f599286471637/Lib/dataclasses.py#L1286-L1288)).

Would this be considered a bug? Would a PR that changes it be considered?

---

<div class="post-metadata">

**Author:** ![OfekShilon](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ofekshilon/32/27054_2.png) [@OfekShilon](https://discuss.python.org/u/OfekShilon)\
**Post date:** [April 28, 2025, 7:47am UTC](https://discuss.python.org/t/dataclasses-has-explicit-hash-broken/89925/2 "2025-04-28T07:47:38Z")

</div>

@ericvsmith ?

---

<div class="post-metadata">

**Author:** ![MegaIng](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/megaing/32/16162_2.png) [@MegaIng](https://discuss.python.org/u/MegaIng)\
**Post date:** [April 28, 2025, 7:55am UTC](https://discuss.python.org/t/dataclasses-has-explicit-hash-broken/89925/3 "2025-04-28T07:55:20Z")

</div>

No, this is expected behavior - a subclass can change wether or not it’s safe to hash instances of that type with zero relation to what superclasses implement or don’t implement.

If this check doesn’t do what you want because you are in some interesting edge cases, you should manually provide the correct parameters.

But also, what is your situation? Can you provide a small example where it behaves incorrectly?

---

<div class="post-metadata">

**Author:** ![OfekShilon](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ofekshilon/32/27054_2.png) [@OfekShilon](https://discuss.python.org/u/OfekShilon)\
**Post date:** [April 28, 2025, 8:23am UTC](https://discuss.python.org/t/dataclasses-has-explicit-hash-broken/89925/4 "2025-04-28T08:23:36Z")

</div>

Here’s an example demonstrating the unexpected behavior:

```python
from dataclasses import dataclass

@dataclass
class A:
    a: int = 1
    b: int = 2
    
    def __hash__ (self) -> int:
        return hash((self.a, self.b))
        
@dataclass
class B(A):
    pass

ss = set([A(), B()]) # TypeError: unhashable type: 'B'

```

This is a toy example of course, and suggesting fixes for it is not the point (here there’s no actual need for a custom ` __hash__ `).

> a subclass can change wether or not it’s safe to hash instances of that type with zero relation to what superclasses implement or don’t implement.

That’s akin to saying dataclass doesn’t support inheritance. Doesn’t it? If it does, why should the support be any different for ` __hash__ `?

---

<div class="post-metadata">

**Author:** ![MegaIng](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/megaing/32/16162_2.png) [@MegaIng](https://discuss.python.org/u/MegaIng)\
**Post date:** [April 28, 2025, 8:27am UTC](https://discuss.python.org/t/dataclasses-has-explicit-hash-broken/89925/5 "2025-04-28T08:27:21Z")

</div>

> [@OfekShilon](#):
>
> This is a toy example of course, and suggesting fixes for it is not the point

Ok, but this is so much of a toy example that I still don’t understand why you want this.

The reason this doesn’t work is because despite there not being any new attributes, `B` generates a new ` __eq__ `. Is that the behavior you want to see changed?

Also, don’t forget that without `frozen=True`, it is strictly unsafe to support hashing.

---

<div class="post-metadata">

**Author:** ![OfekShilon](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ofekshilon/32/27054_2.png) [@OfekShilon](https://discuss.python.org/u/OfekShilon)\
**Post date:** [April 28, 2025, 8:33am UTC](https://discuss.python.org/t/dataclasses-has-explicit-hash-broken/89925/6 "2025-04-28T08:33:40Z")

</div>

Feel free to add `frozen` to the example, it doesn’t change the issue.

When you say ‘this is expected behavior’ (== dataclass ignoring inheritance for a particular method but not all others) is this because it was actually discussed? Was there any agreement that this is the right behavior?

Here is the only discussion I could find leading to this code: [Change dataclasses hashing to use unsafe\_hash boolean (default to False) · Issue #77110 · python/cpython · GitHub](https://github.com/python/cpython/issues/77110)  
There are no mentions of any inheritance considerations for `has_explicit_hash`.

---

<div class="post-metadata">

**Author:** ![MegaIng](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/megaing/32/16162_2.png) [@MegaIng](https://discuss.python.org/u/MegaIng)\
**Post date:** [April 28, 2025, 9:18am UTC](https://discuss.python.org/t/dataclasses-has-explicit-hash-broken/89925/7 "2025-04-28T09:18:25Z")

</div>

> [@OfekShilon](#):
>
> dataclass ignoring inheritance for a particular method but not all others

It is not `dataclasses` “ignoring inheritance”, `dataclasses` is just respecting the behavior of python here. Remove `@dataclass` from the example and instead manually define ` __eq__ ` in **both** `A` and `B` and you will observe the exact same behavior.

See [3. Data model — Python 3.13.3 documentation](https://docs.python.org/3/reference/datamodel.html#object. __hash__ )

---

<div class="post-metadata">

**Author:** ![DavidCEllis](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/davidcellis/32/9789_2.png) [@DavidCEllis](https://discuss.python.org/u/DavidCEllis)\
**Post date:** [April 28, 2025, 9:59am UTC](https://discuss.python.org/t/dataclasses-has-explicit-hash-broken/89925/8 "2025-04-28T09:59:42Z")

</div>

Note if you use `@dataclass(eq=False)` on `B` then it will inherit the ` __hash__ ` method.

`dataclasses` behaviour is trying to emulate the behaviour of Python if the ` __eq__ ` method was written in the class even though it is adding it _after_ the class has been defined.

```python
from dataclasses import dataclass

def eq_method(self, other):
    if type(self) is type(other):
        return self.a == other.a and self.b == other.b
    return NotImplemented

@dataclass
class A:
    a: int = 1
    b: int = 2
    
    def __hash__ (self) -> int:
        return hash((self.a, self.b))
        
class B(A):
    __eq__ = eq_method
      

class C(A):
    pass

C. __eq__ = eq_method # This is roughly how dataclass adds the __eq__ method

print(B. __hash__ ) # None
print(C. __hash__ ) # <function A. __hash__ at 0x76deda1a0220>

```

Dataclasses has to do the check and clear out the hash itself because it wants to look like you’ve written class `B` and not class `C` even though the way it works is more like class `C`.

---

<div class="post-metadata">

**Author:** ![OfekShilon](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ofekshilon/32/27054_2.png) [@OfekShilon](https://discuss.python.org/u/OfekShilon)\
**Post date:** [April 28, 2025, 10:39am UTC](https://discuss.python.org/t/dataclasses-has-explicit-hash-broken/89925/9 "2025-04-28T10:39:59Z")

</div>

I see now. Thank you!
