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 == b
evalúa False
porque realmente se ejecuta a is b
, una prueba de identidad (es decir, "¿Es a
el 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
is
operador 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
object
y que se declaran comoclass A:
,class A():
oclass A(B):
dondeB
es una clase de estilo clásico;clases de estilo nuevo , que heredan de
object
y que se declaran comoclass A(object)
oclass A(B):
dondeB
es 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
Number
es una clase de estilo clásico:n1 == n3
llamadasn1.__eq__
;n3 == n1
llamadasn3.__eq__
;n1 != n3
llamadasn1.__ne__
;n3 != n1
llamadasn3.__ne__
.Y si
Number
es una clase de nuevo estilo:n1 == n3
yn3 == n1
llamarn3.__eq__
;n1 != n3
yn3 != n1
llaman3.__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 elNotImplemented
valor cuando no se admite un tipo de operando. La documentación define elNotImplemented
valor 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
NotImplemented
valor en lugar deFalse
es 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
NotImplemented
como 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
other
es 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.is
pruebas de identidad de objeto. Esto significa que ais
b estaráTrue
en 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_state
me 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