#30333: __hash__  is not inherited from model.Model if __eq__ is defined
-------------------------------------+-------------------------------------
     Reporter:  Adam Janik           |                    Owner:  Carlton
                                     |  Gibson
         Type:  Bug                  |                   Status:  closed
    Component:  Database layer       |                  Version:  2.2
  (models, ORM)                      |
     Severity:  Normal               |               Resolution:  duplicate
     Keywords:                       |             Triage Stage:  Accepted
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
Changes (by Carlton Gibson):

 * status:  assigned => closed
 * resolution:   => duplicate
 * severity:  Release blocker => Normal


Comment:

 Ultimately this is a slightly different version of #30254, which was
 raised after a68ea231012434b522ce45c513d84add516afa60.
 (Will resolve as a duplicate.)

 Prior to the changes in a68ea231012434b522ce45c513d84add516afa60 the model
 would
 inherit **both** `__eq__()` and `__hash__()` from `models.Model` during
 `__new__()`.
 (With `__eq__()` then being replaced in the `add_to_class()` call later.)

 This was incorrect. The Python docs are quite specific:

 > A class that overrides __eq__() and does not define __hash__() will have
 its __hash__() implicitly set to None. When the __hash__() method of a
 class is None, instances of the class will raise an appropriate TypeError
 when a program attempts to retrieve their hash value, and will also be
 correctly identified as unhashable when checking isinstance(obj,
 collections.abc.Hashable).
 >
 > If a class that overrides __eq__() needs to retain the implementation of
 __hash__() from a parent class, the interpreter must be told this
 explicitly by setting __hash__ = <ParentClass>.__hash__.

 By passing the defined `__eq__()` (as well as `__hash__()` if defined)
 into `__new__()`, the change is bringing Django's behaviour into line with
 Python's.

 The changes here were a bug fix, rather than a breaking change per se.
 (Yes, it **is** a
 change in behaviour, but so are all bugfixes, and we don't list them as
 breaking changes in the release notes.)

 I've [https://github.com/django/django/pull/11184 added a couple test
 cases demonstrating the expected behaviour in a PR]. In particular the
 `test_missing_hash_not_inherited()` cases fails on Django 2.1.

 The fix for your project is to explicitly declare the `__hash__ `
 attribute as per the docs above:


 {{{
     __hash__ = models.Model.__hash__
 }}}

 I hope that helps.

-- 
Ticket URL: <https://code.djangoproject.com/ticket/30333#comment:4>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/065.242279d00db9da85e58c22ef8fc2fc45%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to