Python 3 __cmp__ semantic change?

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Hyuga

    #16
    Re: Python 3 __cmp__ semantic change?

    On Nov 21, 4:09 am, Duncan Booth <duncan.bo...@i nvalid.invalidw rote:
    Johannes Bauer <dfnsonfsdu...@ gmx.dewrote:
    Seems it was removed on purpose - I'm sure there was a good reason for
    that, but may I ask why? Instead of the sleek __cmp__ function I had
    earlier, I now have code like:
    >
    def __lt__(self, other):
         return self.__cmp__(ot her) < 0
    >
    def __le__(self, other):
         return self.__cmp__(ot her) < 0
    >
    I hope you actually have <= here.
    >
    >
    >
    def __gt__(self, other):
         return self.__cmp__(ot her) 0
    >
    def __ge__(self, other):
         return self.__cmp__(ot her) >= 0
    >
    Does anyone know the reason why __cmp__ was discarded?
    >
    I think it was because __cmp__ was the backward compatible fallback for
    the newer rich comparison methods and Python 3 cleans up a lot of stuff
    left in just for backward compatibility. In this case it is a cleanup
    too far as in most cases (i.e. those cases where you don't need the full
    complexity of the rich comparisons) __cmp__ is a much simpler solution.
    >
    Seehttp://mail.python.org/pipermail/python-dev/2003-March/034073.html
    for Guido's original thoughts. Also, once upon a time pep-3000 referred
    to the removal of __cmp__ but I can't find it in any of the current
    peps. Seehttp://mail.python.org/pipermail/python-checkins/2004-August/042959.html
    andhttp://mail.python.org/pipermail/python-checkins/2004-August/042972.html
    where the reference to removal of __cmp__ became "Comparison s other than
    ``==`` and ``!=`` between disparate types will raise an exception unless
    explicitly supported by the type" and the reference to Guido's email
    about removing __cmp__ was also removed.
    Guido's primary argument for removing it seems to be that the code for
    supporting both __cmp__ and the rich comparisons is "hairy" and that
    it felt really satisfying to remove. I don't think that's a good
    enough argument. It was hairy because there are a lot of cases to
    check, but I wouldn't say it was crufty. It made sense, and the way
    it worked seemed logical enough. I never ran into any problems with
    it. And by and far the most common case is to implement some total
    ordering for a class.

    Now, as has been pointed out, all you really need to define total
    ordering, at least for sorting, is __eq__ and __lt__, which isn't too
    bad. But you still lose the ability to make any other sort of
    comparison without implementing all the other comparison operators
    too.

    Perhaps the code could be made somewhat simpler like this: If rich
    comparisons are defined, use those and *only* those operators that are
    defined, and don't try to fall back on __cmp__ otherwise. If no rich
    comparisons are defined, just look for __cmp__.

    Comment

    • Arnaud Delobelle

      #17
      Re: Python 3 __cmp__ semantic change?

      Hyuga <hyugaricdeau@g mail.comwrites:
      On Nov 21, 4:09 am, Duncan Booth <duncan.bo...@i nvalid.invalidw rote:
      >Johannes Bauer <dfnsonfsdu...@ gmx.dewrote:
      Seems it was removed on purpose - I'm sure there was a good reason for
      that, but may I ask why? Instead of the sleek __cmp__ function I had
      earlier, I now have code like:
      >>
      def __lt__(self, other):
           return self.__cmp__(ot her) < 0
      >>
      def __le__(self, other):
           return self.__cmp__(ot her) < 0
      >>
      >I hope you actually have <= here.
      >>
      >>
      >>
      def __gt__(self, other):
           return self.__cmp__(ot her) 0
      >>
      def __ge__(self, other):
           return self.__cmp__(ot her) >= 0
      >>
      Does anyone know the reason why __cmp__ was discarded?
      >>
      >I think it was because __cmp__ was the backward compatible fallback for
      >the newer rich comparison methods and Python 3 cleans up a lot of stuff
      >left in just for backward compatibility. In this case it is a cleanup
      >too far as in most cases (i.e. those cases where you don't need the full
      >complexity of the rich comparisons) __cmp__ is a much simpler solution.
      >>
      >Seehttp://mail.python.org/pipermail/python-dev/2003-March/034073.html
      >for Guido's original thoughts. Also, once upon a time pep-3000
      >referred to the removal of __cmp__ but I can't find it in any of the
      >current peps. See
      >http://mail.python.org/pipermail/pyt...st/042959.html
      >and
      >http://mail.python.org/pipermail/pyt...st/042972.html
      >where the reference to removal of __cmp__ became "Comparison s other
      >than ``==`` and ``!=`` between disparate types will raise an
      >exception unless explicitly supported by the type" and the reference
      >to Guido's email about removing __cmp__ was also removed.
      >
      Guido's primary argument for removing it seems to be that the code for
      supporting both __cmp__ and the rich comparisons is "hairy" and that
      it felt really satisfying to remove. I don't think that's a good
      enough argument. It was hairy because there are a lot of cases to
      check, but I wouldn't say it was crufty. It made sense, and the way
      it worked seemed logical enough. I never ran into any problems with
      it. And by and far the most common case is to implement some total
      ordering for a class.
      >
      Now, as has been pointed out, all you really need to define total
      ordering, at least for sorting, is __eq__ and __lt__, which isn't too
      bad. But you still lose the ability to make any other sort of
      comparison without implementing all the other comparison operators
      too.
      As classes can be decorated in Python 3, you can write a decorator to
      make a class totally ordered. Here is a very simplified proof of
      concept such decorator:

      def totally_ordered (cls):
      if not hasattr(cls, '__gt__'):
      def gt(self, other):
      return self != other and not self < other
      cls.__gt__ = gt
      # Do the same with __le__, __ge__
      return cls


      @totally_ordere d
      class Fraction:
      def __init__(self, num, den=1):
      assert den 0, "denomintat or must be 0"
      self.num = num
      self.den = den
      def __eq__(self, other):
      return self.num*other. den == self.den*other. num
      def __lt__(self, other):
      return self.num*other. den < self.den*other. num
      >>q12=Fraction( 1, 2)
      >>q23=Fraction( 2, 3)
      >>q12 < q23
      True
      >>q12 q23
      False

      Granted it's not as efficient as a __cmp__ function.

      --
      Arnaud

      Comment

      • Steven D'Aprano

        #18
        Re: Python 3 __cmp__ semantic change?

        On Fri, 21 Nov 2008 17:26:21 +0000, Arnaud Delobelle wrote:

        [...]
        As classes can be decorated in Python 3, you can write a decorator to
        make a class totally ordered. Here is a very simplified proof of
        concept such decorator:
        >
        def totally_ordered (cls):
        if not hasattr(cls, '__gt__'):
        def gt(self, other):
        return self != other and not self < other
        cls.__gt__ = gt
        # Do the same with __le__, __ge__
        return cls
        >
        >
        @totally_ordere d
        class Fraction:
        def __init__(self, num, den=1):
        assert den 0, "denomintat or must be 0" self.num = num
        self.den = den
        def __eq__(self, other):
        return self.num*other. den == self.den*other. num
        def __lt__(self, other):
        return self.num*other. den < self.den*other. num
        >
        >>>q12=Fraction (1, 2)
        >>>q23=Fraction (2, 3)
        >>>q12 < q23
        True
        >>>q12 q23
        False
        >
        Granted it's not as efficient as a __cmp__ function.
        What makes you say that? What do you mean by "efficient" ? Are you talking
        about memory footprint, runtime speed, disk-space, programmer efficiency,
        algorithmic complexity, or something else?

        As I see it, a __cmp__ method would be written something like this:

        def __cmp__(self, other):
        return cmp(self.num*ot her.den, self.den*other. num)

        which presumably would save you a trivial amount of source code (and
        hence memory footprint, disk-space and programmer efficiency), but the
        algorithmic complexity is identical and the runtime speed might even be
        trivially slower due to the extra function call.

        If your major concern is to reduce the amount of repeated code in the
        methods, then there's no reason why you can't write a __cmp__ method as
        above and then call it from your rich comparisons:

        def __eq__(self, other):
        return self.__cmp__(ot her) == 0
        def __lt__(self, other):
        return self.__cmp__(ot her) < 0

        and if you really want to be concise:

        __gt__ = lambda s, o: s.__cmp__(o) 0
        __ge__ = lambda s, o: s.__cmp__(o) >= 0
        __le__ = lambda s, o: s.__cmp__(o) <= 0





        --
        Steven

        Comment

        Working...