#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 django-updates+unsubscr...@googlegroups.com.
To post to this group, send email to django-updates@googlegroups.com.
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