[Python-ideas] Automatic total ordering

Jason Orendorff jason.orendorff at gmail.com
Thu Oct 16 21:02:12 CEST 2008


Terry Reedy wrote:
> In 3.0, the actual definitions are the C equivalent of
>
> class object():
>  def __eq__(self,other): return id(self) == id(other)
>  def __ne__(self,other): return id(self) != id(other)
>  def __lt__(self,other): return NotImplemented

Not exactly.  For example, object().__eq__(object()) ==>
NotImplemented, not False; and __ne__ calls __eq__, at least
sometimes.  The best I can do is:

    def __eq__(self, other):
        if self is other:
            return True
        return NotImplemented

    def __ne__(self, other):
        # calls PyObject_RichCompare(self, other, Py_EQ)...
        eq = (self == other)  # ...which is kinda like this
        if eq is NotImplemented:  # is this even possible?
            return NotImplemented
        return not eq

    def __lt__(self, other):
        return NotImplemented

This behavior makes sense to me, except for __ne__ calling
PyObject_RichCompare, which seems like a bug.  If I understand
correctly, object.__ne__ should be more like this:

    def __ne__(self, other):
        eq = self.__eq__(other)
        if eq is NotImplemented: return NotImplemented
        return not eq

(When I write something like `self.__eq__(other)`, here and below,
what I really mean is something more like what half_richcompare does,
avoiding the instance dict and returning NotImplemented if the method
is not found.)

The current behavior causes __eq__ to be called four times in cases
where two seems like enough.  Rather confusing.

So I think the proposal is to change the other three methods to try
using __lt__ and __eq__ in a similar way:

    def __le__(self, other):
        # Note: NotImplemented is truthy, so if either of these
        # returns NotImplemented, __le__ returns NotImplemented.
        return self.__lt__(other) or self.__eq__(other)

    def __gt__(self, other):
        # 'and' isn't quite as convenient for us here as 'or'
        # was above, so spell out what we want:
        lt = self.__lt__(other)
        if lt is NotImplemented: return NotImplemented
        if lt: return False
        eq = self.__eq__(other)
        if eq is NotImplemented: return NotImplemented
        return not eq

   def __ge__(self, other):
        lt = self.__lt__(other)
        if lt is NotImplemented: return NotImplemented
        return not lt

These methods never call __eq__ without first calling __lt__.  That's
significant: if __lt__ always returns NotImplemented--the
default--then these methods should always return NotImplemented too:
we don't want to get a bogus True or False result based on a
successful call to __eq__.

It would also be nice to stop telling people that:
    x.__eq__(y) <==> x==y
    x.__ne__(y) <==> x!=y
and so forth, in the docstrings and the language reference, as that's
an awfully loose approximation of the truth.

-j



More information about the Python-ideas mailing list