Al escribir clases personalizadas, a menudo es importante permitir la equivalencia mediante los operadores ==y !=. En Python, esto es posible implementando los métodos especiales __eq__y __ne__, respectivamente. La forma más fácil que he encontrado para hacer esto es el siguiente método:
class Foo:
def __init__(self, item):
self.item = item
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.__dict__ == other.__dict__
else:
return False
def __ne__(self, other):
return not self.__eq__(other)
¿Conoces formas más elegantes de hacer esto? ¿Conoces alguna desventaja particular al usar el método anterior de comparar __dict__s?
Nota : un poco de aclaración: cuando __eq__y __ne__no están definidos, encontrará este comportamiento:
>>> a = Foo(1)
>>> b = Foo(1)
>>> a is b
False
>>> a == b
False
Es decir, a == bevalúa Falseporque realmente se ejecuta a is b, una prueba de identidad (es decir, "¿Es ael mismo objeto que b?").
Cuando __eq__y __ne__están definidos, encontrará este comportamiento (que es el que buscamos):
>>> a = Foo(1)
>>> b = Foo(1)
>>> a is b
False
>>> a == b
True
python
equality
equivalence
gotgenes
fuente
fuente

isoperador para distinguir la identidad del objeto de la comparación de valores.Respuestas:
Considere este simple problema:
Entonces, Python por defecto usa los identificadores de objeto para las operaciones de comparación:
Anular la
__eq__función parece resolver el problema:En Python 2 , recuerde siempre anular la
__ne__función también, como dice la documentación :En Python 3 , esto ya no es necesario, como dice la documentación :
Pero eso no resuelve todos nuestros problemas. Agreguemos una subclase:
Nota: Python 2 tiene dos tipos de clases:
clases de estilo clásico (o estilo antiguo ), que no heredan de
objecty que se declaran comoclass A:,class A():oclass A(B):dondeBes una clase de estilo clásico;clases de estilo nuevo , que heredan de
objecty que se declaran comoclass A(object)oclass A(B):dondeBes una clase de estilo nuevo. Python 3 solo tiene clases de estilo nuevo que se declaran comoclass A:,class A(object):oclass A(B):.Para las clases de estilo clásico, una operación de comparación siempre llama al método del primer operando, mientras que para las clases de estilo nuevo, siempre llama al método del operando de la subclase, independientemente del orden de los operandos .
Entonces aquí, si
Numberes una clase de estilo clásico:n1 == n3llamadasn1.__eq__;n3 == n1llamadasn3.__eq__;n1 != n3llamadasn1.__ne__;n3 != n1llamadasn3.__ne__.Y si
Numberes una clase de nuevo estilo:n1 == n3yn3 == n1llamarn3.__eq__;n1 != n3yn3 != n1llaman3.__ne__.Para solucionar el problema de no conmutatividad de los operadores
==y!=para las clases de estilo clásico de Python 2, los métodos__eq__y__ne__deberían devolver elNotImplementedvalor cuando no se admite un tipo de operando. La documentación define elNotImplementedvalor como:En este caso, el operador delega la operación de comparación al método reflejado del otro operando. La documentación define los métodos reflejados como:
El resultado se ve así:
Devolver el
NotImplementedvalor en lugar deFalsees lo correcto incluso para las clases de estilo nuevo si se desea la conmutatividad de los operadores==y!=cuando los operandos son de tipos no relacionados (sin herencia).¿Ya llegamos? No exactamente. ¿Cuántos números únicos tenemos?
Los conjuntos usan los hash de los objetos y, de forma predeterminada, Python devuelve el hash del identificador del objeto. Intentemos anularlo:
El resultado final se ve así (agregué algunas afirmaciones al final para la validación):
fuente
hash(tuple(sorted(self.__dict__.items())))no funcionará si hay objetos no hashable entre los valores deself.__dict__(es decir, si alguno de los atributos del objeto se establece en, digamos, alist).__ne__usando en==lugar de__eq__.__ne__: "Por defecto,__ne__()delega__eq__()e invierte el resultado a menos que seaNotImplemented". 2. Si todavía se desea implementar__ne__, una aplicación más genérica (la utilizada por Python 3 creo) es:x = self.__eq__(other); if x is NotImplemented: return x; else: return not x. 3. Lo dado__eq__y las__ne__implementaciones son subóptimas:if isinstance(other, type(self)):da 22__eq__y 10__ne__llamadas, mientrasif isinstance(self, type(other)):que daría 16__eq__y 6__ne__llamadas.Debes tener cuidado con la herencia:
Verifique los tipos más estrictamente, así:
Además de eso, su enfoque funcionará bien, para eso están los métodos especiales.
fuente
NotImplementedcomo sugiere siempre causarásuperclass.__eq__(subclass), que es el comportamiento deseado.if other is self. Esto evita la comparación de diccionario más larga y puede ser un gran ahorro cuando los objetos se utilizan como claves de diccionario.__hash__()La forma en que describe es la forma en que siempre lo he hecho. Como es totalmente genérico, siempre puede dividir esa funcionalidad en una clase mixin y heredarla en las clases donde desee esa funcionalidad.
fuente
otheres de una subclase deself.__class__.__dict__comparación es qué sucede si tiene un atributo que no desea tener en cuenta en su definición de igualdad (por ejemplo, una identificación de objeto única o metadatos como un sello de tiempo creado).No es una respuesta directa, pero parecía lo suficientemente relevante como para ser agregada, ya que en ocasiones ahorra un poco de tedio detallado. Corte directamente de los documentos ...
functools.total_ordering (cls)
Dada una clase que define uno o más métodos de ordenación de comparación, este decorador de clase proporciona el resto. Esto simplifica el esfuerzo involucrado en la especificación de todas las posibles operaciones de comparación enriquecida:
La clase debe definir uno de
__lt__(),__le__(),__gt__(), o__ge__(). Además, la clase debe proporcionar un__eq__()método.Nuevo en la versión 2.7
fuente
No tiene que anular ambos
__eq__y__ne__solo puede anular,__cmp__pero esto tendrá una implicación en el resultado de ==,! ==, <,> y así sucesivamente.ispruebas de identidad de objeto. Esto significa que aisb estaráTrueen el caso en que a y b tengan la referencia al mismo objeto. En python siempre tiene una referencia a un objeto en una variable, no el objeto real, por lo que esencialmente para que a b sea verdadero, los objetos en ellos deben ubicarse en la misma ubicación de memoria. ¿Cómo y, lo que es más importante, por qué anularías este comportamiento?Editar: no sabía que
__cmp__se eliminó de Python 3, así que evítalo.fuente
De esta respuesta: https://stackoverflow.com/a/30676267/541136 Lo he demostrado, aunque es correcto definirlo
__ne__en términos__eq__, en lugar deDeberías usar:
fuente
Creo que los dos términos que está buscando son igualdad (==) e identidad (es). Por ejemplo:
fuente
La prueba 'is' probará la identidad utilizando la función incorporada 'id ()' que esencialmente devuelve la dirección de memoria del objeto y, por lo tanto, no se puede cargar.
Sin embargo, en el caso de probar la igualdad de una clase, es probable que desee ser un poco más estricto sobre sus pruebas y solo compare los atributos de datos en su clase:
Este código solo comparará miembros de datos no funcionales de su clase, así como omitirá todo lo privado que generalmente es lo que desea. En el caso de Plain Old Python Objects, tengo una clase base que implementa __init__, __str__, __repr__ y __eq__ para que mis objetos POPO no lleven la carga de toda esa lógica extra (y en la mayoría de los casos idéntica).
fuente
__eq__se declarará enCommonEqualityMixin(ver la otra respuesta). Encontré esto particularmente útil al comparar instancias de clases derivadas de Base en SQLAlchemy. Para no comparar_sa_instance_stateme cambiékey.startswith("__")):akey.startswith("_")):. También tenía algunas referencias inversas y la respuesta de Algorias generó una recursión interminable. Así que nombré todas las referencias anteriores comenzando'_'para que también se omitan durante la comparación. NOTA: en Python 3.x cambieiteritems()aitems().__dict__una instancia no tiene nada que comience a__menos que sea definida por el usuario. Cosas como__class__,__init__etc. no están en la instancia__dict__, sino en su clase '__dict__. OTOH, los atributos privados pueden comenzar fácilmente__y probablemente deberían usarse para__eq__. ¿Puede aclarar qué estaba tratando de evitar exactamente al omitir los__atributos prefijados?En lugar de usar subclases / mixins, me gusta usar un decorador de clase genérico
Uso:
fuente
Esto incorpora los comentarios sobre la respuesta de Algorias y compara los objetos por un solo atributo porque no me importa todo el dict.
hasattr(other, "id")debe ser cierto, pero sé que es porque lo configuré en el constructor.fuente