Understanding why object defines rich comparison methods

Why does `object` define rich comparison dunders `__lt__` etc? As far as I can tell, `object.__lt__` etc always return NotImplemented. Merely inheriting from object isn't enough to have comparisons work. So why do they exist at all? Other "do nothing" dunders such as `__add__` aren't defined. I've tried searching for answers, and looked at the source code for rich comparisons here: https://github.com/python/cpython/blob/3.8/Objects/object.c read the information here and in the PEP: https://docs.python.org/3/c-api/object.html#c.PyObject_RichCompare https://www.python.org/dev/peps/pep-0207/ I'm not sure if I'm looking in the right places, or if I even understand exactly what I'm looking at, but I think the relevent code is in typeobject.c, which defines a type: PyTypeObject PyBaseObject_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "object", /* tp_name */ ... and then sets tp_richcompare to `object_richcompare`. I guess that is enough to define the dunder methods? Since (as far as I can tell) they don't do anything, why do they exist? Thanks in advance. -- Steve

NotImplemented is like a pure virtual function; failing to implement it tells you that you forgot part of the contract, except at runtime instead of compile time. So if you never need them, you're free to elide them, but if you want full compatibility, you need to implement every part of it. If someone tried to Obj + Obj and that's just completely nonsensical for the type, then NotImplemented is a reasonable message, as opposed to NameError which can be rather confusing. Alternately, you can implement it and tell the caller exactly why it's not implemented. It's also useful for anyone looking to subclass by immediately showing them every base method they might need to implement. -Em

22.09.20 12:48, Steven D'Aprano пише:
Why does `object` define rich comparison dunders `__lt__` etc?
As far as I can tell, `object.__lt__` etc always return NotImplemented. Merely inheriting from object isn't enough to have comparisons work. So why do they exist at all? Other "do nothing" dunders such as `__add__` aren't defined.
Because object.__eq__ and object.__ne__ exist. If you define slot tp_richcompare in C, it is exposed as 6 methods __eq__, __ne__, __lt__, __le__, __gt__ and __ge__. It is not possible to determine that __lt__() always returns NotImplemented without running it.

On Tue, Sep 22, 2020 at 02:13:46PM +0300, Serhiy Storchaka wrote:
22.09.20 12:48, Steven D'Aprano пише:
Why does `object` define rich comparison dunders `__lt__` etc?
Because object.__eq__ and object.__ne__ exist. If you define slot tp_richcompare in C, it is exposed as 6 methods __eq__, __ne__, __lt__, __le__, __gt__ and __ge__. It is not possible to determine that __lt__() always returns NotImplemented without running it.
Ah, thank you Serhiy, I thought it might be something like that. Presumably back when rich comparisons were added, the choice would have been: - add one tp_richcompare slot to support all six methods; or - add six slots, one for each individual dunder in which case the first option wastes much less space. Is that a reasonable understanding of the motive? -- Steve

On Tue., 22 Sep. 2020, 10:25 pm Steven D'Aprano, <steve@pearwood.info> wrote:
On Tue, Sep 22, 2020 at 02:13:46PM +0300, Serhiy Storchaka wrote:
22.09.20 12:48, Steven D'Aprano пише:
Why does `object` define rich comparison dunders `__lt__` etc?
Because object.__eq__ and object.__ne__ exist. If you define slot tp_richcompare in C, it is exposed as 6 methods __eq__, __ne__, __lt__, __le__, __gt__ and __ge__. It is not possible to determine that __lt__() always returns NotImplemented without running it.
Ah, thank you Serhiy, I thought it might be something like that.
Presumably back when rich comparisons were added, the choice would have been:
- add one tp_richcompare slot to support all six methods; or
- add six slots, one for each individual dunder
in which case the first option wastes much less space. Is that a reasonable understanding of the motive?
Looking at https://www.python.org/dev/peps/pep-0207/ I suspect the possibility of having 6 different slots didn't even come up (or if it did, it wasn't considered seriously enough to be mentioned in the PEP). Instead, the design was an evolution of the old boolean-only tp_compare slot to allow NumPy to return arrays from array comparison operations. Cheers, Nick.
-- Steve _______________________________________________ Python-Dev mailing list -- python-dev@python.org To unsubscribe send an email to python-dev-leave@python.org https://mail.python.org/mailman3/lists/python-dev.python.org/ Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/6HXL72CE... Code of Conduct: http://python.org/psf/codeofconduct/

On 23/09/20 12:20 am, Steven D'Aprano wrote:
Presumably back when rich comparisons were added, the choice would have been:
- add one tp_richcompare slot to support all six methods; or
- add six slots, one for each individual dunder
in which case the first option wastes much less space.
I don't know the exact reasons, but it might also have been because the implementations of the six dunders are usually very closely related, so having just one function to implement at the C level is a lot easier for most types. Also remember that before tp_richcompare existed there was only tp_compare, which also handled all the comparisons, so tp_richcompare was likely seen as a generalisation of that. -- Greg

23.09.20 03:05, Greg Ewing пише:
On 23/09/20 12:20 am, Steven D'Aprano wrote:
Presumably back when rich comparisons were added, the choice would have been:
- add one tp_richcompare slot to support all six methods; or
- add six slots, one for each individual dunder
in which case the first option wastes much less space.
I don't know the exact reasons, but it might also have been because the implementations of the six dunders are usually very closely related, so having just one function to implement at the C level is a lot easier for most types.
Also remember that before tp_richcompare existed there was only tp_compare, which also handled all the comparisons, so tp_richcompare was likely seen as a generalisation of that.
And before dunders __eq__, __lt__, etc there was a single dunder __cmp__.

On 22/09/2020 12:13, Serhiy Storchaka wrote:
Because object.__eq__ and object.__ne__ exist.
I didn't quite get the logic of this. In case anyone (not Steven) is still puzzled as I was, I think one could say: ... because tp_richcompare is filled, *so that* object.__eq__ and object.__ne__ will exist to support default (identity) object comparison. And then __lt__, etc. get defined because, as Serhiy says, ...
If you define slot tp_richcompare in C, it is exposed as 6 methods __eq__, __ne__, __lt__, __le__, __gt__ and __ge__.
By "exposed" we mean each descriptor (PyWrapperDescrObject) points to a different C function (generated by the RICHCMP_WRAPPER macro). The function summons the tp_richcompare slot function in the left object's type, with arguments (left, right, op). Simple (not). Jeff Allen
participants (6)
-
Emily Bowman
-
Greg Ewing
-
Jeff Allen
-
Nick Coghlan
-
Serhiy Storchaka
-
Steven D'Aprano