[Python-3000] Please re-add __cmp__ to python 3000

Adam Olsen rhamph at gmail.com
Wed Oct 31 02:57:17 CET 2007


On 10/30/07, Steven Bethard <steven.bethard at gmail.com> wrote:
> On 10/30/07, Adam Olsen <rhamph at gmail.com> wrote:
> > It's clear to me that the opposition to removing __cmp__ comes down to
> > "make the common things easy and the rare things possible".  Removing
> > __cmp__ means one of the common things (total ordering) becomes hard.
>
> I don't really think that's it.  I don't see much of a difference in
> difficulty between writing::
>
>     class C(TotalOrderingMixin):
>         def __lt__(self, other):
>             self.foo < other.foo
>         def __eq__(self, other):
>             self.foo == other.foo
>
> or writing [1] ::
>
>     class C(object):
>         def __cmp__(self, other):
>             if self.foo < other.foo:
>                 return -1
>             elif self.foo < other.foo:
>                 return 1
>             else:
>                 return 0
>
> The main motivation seems really to be efficiency for a particular
> task.  For some tasks, e.g. sorting, you really only need __lt__, so
> going through __cmp__ will just be slower. For other tasks, e.g.
> comparing objects with several components, you know you have to do
> both the __lt__ and __eq__ comparisons, so it would be wasteful to
> make two calls when you know you could do it in one through __cmp__.
>
> So it's not really about making things easier or harder, it's about
> making the most efficient tool for the task available.
>
> Steve
>
> [1] Yes, of course, you could just write cmp(self.foo, other.foo), but
> this is how it's been written in the rest of the thread, so I have to
> assume that it's more representative of real code.

cmp and __cmp__ are doomed, due to unorderable types now raising exceptions:

>>> cmp(3, 'hello')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: int() < str()
>>> 3 == 'hello'
False

A mixin for __cmp__ would be sufficient for scalars (where you can
avoid this exception and your size is constant), but not for
containers (which need to avoid inappropriate types and wish to avoid
multiple passes.)

I don't think __richcmp__ makes the process quite as simple as we want though:

    class C(RichCmpMixin):
        def __richcmp__(self, other, mode):
            if not isinstance(other, C):
                return NotImplemented
            for a, b in zip(self.data, other.data):
                result = richcmp(a, b, mode)
                # XXX how do I know when to stop if all I'm doing is a
                # <= comparison?  cmp() is much easier!
            return richcmp(len(self.data), len(other.data), mode)

If you standardize the meaning of the return values, rather than
changing meaning based upon arguments, the whole thing works much
better.  A simple ordered flag indicates the extent of your
comparison.  Returning a false value always means equal, while
returning a true value means unequal possibly with a specific
ordering.

    class C:
        def __richcmp__(self, other, ordered):
            if not isinstance(other, C):
                return NotImplemented
            for a, b in zip(self.data, other.data):
                result = richcmp(a, b, ordered)
                if result:
                    return result
            return richcmp(len(self.data), len(other.data), ordered)

It also occurs to me that, if a type doesn't use symmetric
comparisons, it should raise an exception rather than silently doing
the wrong thing.  To do that you need to know explicitly when ordering
is being done (which richcmp/__richcmp__ does.)


-- 
Adam Olsen, aka Rhamphoryncus


More information about the Python-3000 mailing list