Opened 5 years ago

Closed 5 years ago

#30651 closed Cleanup/optimization (fixed)

__eq__ should return NotImplemented when equality cannot be checked.

Reported by: Elizabeth Uselton Owned by: Elizabeth Uselton
Component: Core (Other) Version: dev
Severity: Normal Keywords:
Cc: Elizabeth Uselton Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by Elizabeth Uselton)

Model.__eq__ never returns NotImplemented if it encounters an object it doesn't know how to compare against. Instead, if the object it is comparing to is not a Django Model, it automatically returns False.

https://github.com/django/django/blob/master/django/db/models/base.py#L526

According to the Python 3 data model reference, a __eq__ should return NotImplemented
https://docs.python.org/3/reference/datamodel.html#object.__eq__
If a.__eq__(b) returns NotImplemented, then b.__eq__(a) will be tried. If both return NotImplemented, then an is check is performed, and if that fails it returns False.

This may seem like a relatively innocuous difference, but it can cause some nasty bugs. The most obvious is that for testing,
<A Django Model> == mock.ANY returns False, since by not returning NotImplemented it never even looks at the overridden __eq__ on ANY.

Change History (9)

comment:1 by Elizabeth Uselton, 5 years ago

Description: modified (diff)

comment:2 by Mariusz Felisiak, 5 years ago

Component: Database layer (models, ORM)Core (Other)
Summary: Model.__eq__ never returns NotImplemented__eq__ should return NotImplemented when equality cannot be checked.
Triage Stage: UnreviewedAccepted
Type: BugCleanup/optimization
Version: 2.2master

Thanks for this ticket, sounds reasonable. We should be consistent and make this change also for other classes e.g. Migration, CheckConstraint, UniqueConstraint, BaseValidator etc. (check basically all __eq__() methods).

comment:3 by Elizabeth Uselton, 5 years ago

Great! I'll be sure to do those too. I'm very excited to be making my first Django contribution.

comment:4 by Elizabeth Uselton, 5 years ago

Cc: Elizabeth Uselton added
Has patch: set
Patch needs improvement: set

comment:5 by Elizabeth Uselton, 5 years ago

I've added a PR that only has a few of the classes changed, since at a glance there are like 70 classes with __eq__ overwritten, and I'd prefer to get the high value Model change merged (people seem much more likely to compare Model in tests than other classes) and get feedback on the tests I added (whether they're in the right place, and whether I'd need to add them for every False to NotImplemented change).

comment:6 by Mariusz Felisiak, 5 years ago

Patch needs improvement: unset
Triage Stage: AcceptedReady for checkin

comment:7 by Mariusz Felisiak <felisiak.mariusz@…>, 5 years ago

In 0d4b40fe:

Refs #30651 -- Added tests for Message.eq().

comment:8 by Mariusz Felisiak <felisiak.mariusz@…>, 5 years ago

In 6475e63:

Refs #30651 -- Added tests for Prefetch.eq().

comment:9 by Mariusz Felisiak <felisiak.mariusz@…>, 5 years ago

Resolution: fixed
Status: assignedclosed

In 54ea290e:

Fixed #30651 -- Made eq() methods return NotImplemented for not implemented comparisons.

Changed eq to return NotImplemented instead of False if compared to
an object of the same type, as is recommended by the Python data model
reference. Now these models can be compared to ANY (or other objects
with eq overwritten) without returning False automatically.

Note: See TracTickets for help on using tickets.
Back to Top