#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.