lt en lugar de cmp


Python 2.x tiene dos formas de sobrecargar los operadores de comparación, __cmp__ o los "operadores de comparación ricos" como__lt__. Se dice que las sobrecargas de comparación ricas son preferidas, pero ¿por qué es así?

Los operadores de comparación enriquecidos son más fáciles de implementar cada uno, pero debe implementar varios de ellos con una lógica casi idéntica. Sin embargo, si puede utilizar el builtin cmp y el orden de tupla, entonces __cmp__ es bastante simple y cumple con todos los comparaciones:

class A(object):
  def __init__(self, name, age, other):
    self.name = name
    self.age = age
    self.other = other
  def __cmp__(self, other):
    assert isinstance(other, A) # assumption for this example
    return cmp((self.name, self.age, self.other),
               (other.name, other.age, other.other))

Esta simplicidad parece satisfacer mis necesidades mucho mejor que sobrecargar los 6(!) de las comparaciones ricas. (Sin embargo, puedes reducirlo a "solo" 4 si confías en el "argumento intercambiado"/comportamiento reflejado, pero eso resulta en un aumento neto de complicaciones, en mi humilde opinión.)

¿Hay alguna dificultad imprevista de la que deba ser consciente si solo sobrecarga __cmp__?

Entiendo la <, <=, ==, etc. los operadores pueden ser sobrecargado para otros fines, y puede devolver cualquier objeto que les guste. No estoy preguntando sobre los méritos de ese enfoque, sino solo sobre las diferencias al usar estos operadores para comparaciones en el mismo sentido que significan para los números.

Actualización: Como Christopher señaló, cmp está desapareciendo en 3.x. ¿Hay alguna alternativa que haga que la implementación de comparaciones sea tan fácil como la anterior __cmp__?

Author: Community, 2009-06-30

5 answers

Sí, es fácil implementar todo en términos de, por ejemplo, __lt__ con una clase mixin (o una metaclase, o un decorador de clases si su gusto funciona de esa manera).

Por ejemplo:

class ComparableMixin:
  def __eq__(self, other):
    return not self<other and not other<self
  def __ne__(self, other):
    return self<other or other<self
  def __gt__(self, other):
    return other<self
  def __ge__(self, other):
    return not self<other
  def __le__(self, other):
    return not other<self

Ahora su clase puede definir solo __lt__ y multiplicar heredar de ComparableMixin (después de cualquier otra base que necesite, si la hay). Un decorador de clase sería bastante similar, simplemente insertando funciones similares como atributos de la nueva clase que está decorando (el resultado podría ser microscópicamente más rápido en tiempo de ejecución, a un costo igual de minutos en términos de memoria).

Por supuesto, si su clase tiene alguna forma particularmente rápida de implementar (por ejemplo) __eq__ y __ne__, debería definirlas directamente para que las versiones de mixin no se usen ( por ejemplo, ese es el caso de dict) {de hecho __ne__ bien podría definirse para facilitar eso como: {[15]]}

def __ne__(self, other):
  return not self == other

Pero en el código anterior quería mantener la simetría agradable de usar solo <; -). En cuanto a por qué __cmp__ tuvo que ir, ya que hicimos tenemos __lt__ y amigos, ¿por qué mantener otra forma diferente de hacer exactamente lo mismo? Es mucho peso muerto en cada tiempo de ejecución de Python (Classic, Jython, IronPython, PyPy, ...). El código que definitivamente no tendrá errores es el código que no está ahí wh de ahí el principio de Python de que idealmente debería haber una forma obvia de realizar una tarea (C tiene el mismo principio en la sección "Espíritu de C" del estándar ISO, por cierto).

Esto no significa que salgamos de nuestro camino para prohibir cosas (por ejemplo, casi equivalencia entre mixins y decoradores de clases para algunos usos), pero definitivamente significa que no nos gusta llevar código en los compiladores y/o tiempos de ejecución que existen redundantemente solo para soportar múltiples enfoques equivalentes para realizar exactamente la misma tarea.

Edición adicional: en realidad hay una manera aún mejor de proporcionar comparación y hash para muchas clases, incluida la del método question {a __key__, como mencioné en mi comentario a la pregunta. Dado que nunca llegué a escribir el PEP para él, actualmente debes implementarlo con un Mixin (&c) si te gusta:

class KeyedMixin:
  def __lt__(self, other):
    return self.__key__() < other.__key__()
  # and so on for other comparators, as above, plus:
  def __hash__(self):
    return hash(self.__key__())

Es un caso muy común que las comparaciones de una instancia con otras instancias se reduzcan a comparar una tupla para cada una con unos pocos campos -- y luego, el hash debe implementarse exactamente sobre la misma base. El método especial __key__ se dirige directamente a las necesidades.

 82
Author: Alex Martelli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-06-30 02:07:47

Para simplificar este caso hay un decorador de clases en Python 2.7+/3.2+, functools.total_ordering, que se puede usar para implementar lo que Alex sugiere. Ejemplo de los documentos:

@total_ordering
class Student:
    def __eq__(self, other):
        return ((self.lastname.lower(), self.firstname.lower()) ==
                (other.lastname.lower(), other.firstname.lower()))
    def __lt__(self, other):
        return ((self.lastname.lower(), self.firstname.lower()) <
                (other.lastname.lower(), other.firstname.lower()))
 42
Author: jmagnusson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-25 09:01:11

Esto está cubierto por Comparaciones ricas en PEP 207

También, __cmp__ desaparece en python 3.0. ( Tenga en cuenta que no está presente en http://docs.python.org/3.0/reference/datamodel.html pero a http://docs.python.org/2.7/reference/datamodel.html )

 8
Author: Christopher,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-06-29 01:20:06

Inspirado en Alex Martelli ComparableMixin & KeyedMixin respuestas, se me ocurrió la siguiente mezcla. Le permite implementar un único método _compare_to(), que utiliza comparaciones basadas en claves similar a KeyedMixin, pero permite que su clase elija la clave de comparación más eficiente basada en el tipo de other. (Tenga en cuenta que este mixin no ayuda mucho para los objetos que se pueden probar para la igualdad, pero no el orden).

class ComparableMixin(object):
    """mixin which implements rich comparison operators in terms of a single _compare_to() helper"""

    def _compare_to(self, other):
        """return keys to compare self to other.

        if self and other are comparable, this function 
        should return ``(self key, other key)``.
        if they aren't, it should return ``None`` instead.
        """
        raise NotImplementedError("_compare_to() must be implemented by subclass")

    def __eq__(self, other):
        keys = self._compare_to(other)
        return keys[0] == keys[1] if keys else NotImplemented

    def __ne__(self, other):
        return not self == other

    def __lt__(self, other):
        keys = self._compare_to(other)
        return keys[0] < keys[1] if keys else NotImplemented

    def __le__(self, other):
        keys = self._compare_to(other)
        return keys[0] <= keys[1] if keys else NotImplemented

    def __gt__(self, other):
        keys = self._compare_to(other)
        return keys[0] > keys[1] if keys else NotImplemented

    def __ge__(self, other):
        keys = self._compare_to(other)
        return keys[0] >= keys[1] if keys else NotImplemented
 0
Author: Eli Collins,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-08-30 01:52:08

(Editado el 6/17/17 para tener en cuenta los comentarios.)

Probé la respuesta mixin comparable anterior. Me metí en problemas con "Ninguno". Aquí hay una versión modificada que maneja comparaciones de igualdad con "Ninguno". (No vi ninguna razón para molestarse con las comparaciones de desigualdad con Ninguna como falta de semántica):


class ComparableMixin(object):

    def __eq__(self, other):
        if not isinstance(other, type(self)): 
            return NotImplemented
        else:
            return not self<other and not other<self

    def __ne__(self, other):
        return not __eq__(self, other)

    def __gt__(self, other):
        if not isinstance(other, type(self)): 
            return NotImplemented
        else:
            return other<self

    def __ge__(self, other):
        if not isinstance(other, type(self)): 
            return NotImplemented
        else:
            return not self<other

    def __le__(self, other):
        if not isinstance(other, type(self)): 
            return NotImplemented
        else:
            return not other<self    
 0
Author: Gabriel Ferrer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-17 21:20:15