Lors de l'écriture de classes personnalisées, il est souvent important de permettre l'équivalence au moyen des opérateurs ==
et !=
. En Python, cela est rendu possible en implémentant respectivement les méthodes spéciales __eq__
et __ne__
. La méthode la plus simple que j'ai trouvée pour ce faire est la méthode suivante:
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)
Connaissez-vous des moyens plus élégants de le faire? Connaissez-vous des inconvénients particuliers à utiliser la méthode ci-dessus pour comparer __dict__
s?
Remarque : Un peu de clarification - lorsque __eq__
et ne __ne__
sont pas définis, vous trouverez ce comportement:
>>> a = Foo(1)
>>> b = Foo(1)
>>> a is b
False
>>> a == b
False
C'est-à-dire, a == b
évalue False
parce qu'il fonctionne vraiment a is b
, un test d'identité (c.-à-d. "Est-ce a
le même objet que b
?").
Lorsque __eq__
et __ne__
sont définis, vous trouverez ce comportement (qui est celui que nous recherchons):
>>> a = Foo(1)
>>> b = Foo(1)
>>> a is b
False
>>> a == b
True
la source
is
opérateur pour distinguer l'identité de l'objet de la comparaison de valeurs.Réponses:
Considérez ce problème simple:
Ainsi, Python utilise par défaut les identificateurs d'objet pour les opérations de comparaison:
Remplacer la
__eq__
fonction semble résoudre le problème:En Python 2 , n'oubliez pas de remplacer également la
__ne__
fonction, comme l' indique la documentation :En Python 3 , cela n'est plus nécessaire, comme l' indique la documentation :
Mais cela ne résout pas tous nos problèmes. Ajoutons une sous-classe:
Remarque: Python 2 a deux types de classes:
les classes de style classique (ou à l' ancienne ), quin'héritent pas de
object
et qui sont déclarées commeclass A:
,class A():
ouclass A(B):
où seB
trouve une classe de style classique;classes de nouveau style , qui héritent de
object
et qui sont déclarées commeclass A(object)
ouclass A(B):
oùB
est une classe de nouveau style. Python 3 n'a que des classes de nouveau style qui sont déclarées commeclass A:
,class A(object):
ouclass A(B):
.Pour les classes de style classique, une opération de comparaison appelle toujours la méthode du premier opérande, tandis que pour les classes de nouveau style, elle appelle toujours la méthode de l'opérande de sous-classe, quel que soit l'ordre des opérandes .
Voici donc, si
Number
c'est une classe de style classique:n1 == n3
appelsn1.__eq__
;n3 == n1
appelsn3.__eq__
;n1 != n3
appelsn1.__ne__
;n3 != n1
les appelsn3.__ne__
.Et si
Number
c'est une classe de nouveau style:n1 == n3
etn3 == n1
appelezn3.__eq__
;n1 != n3
etn3 != n1
appelezn3.__ne__
.Pour résoudre le problème de non-commutativité des opérateurs
==
et!=
pour les classes de style classique Python 2, les méthodes__eq__
et__ne__
doivent renvoyer laNotImplemented
valeur lorsqu'un type d'opérande n'est pas pris en charge. La documentation définit laNotImplemented
valeur comme:Dans ce cas, l'opérateur délègue l'opération de comparaison à la méthode reflétée de l' autre opérande. La documentation définit les méthodes reflétées comme:
Le résultat ressemble à ceci:
Renvoyer la
NotImplemented
valeur à la place deFalse
est la bonne chose à faire même pour les classes de nouveau style si la commutativité des opérateurs==
et!=
est souhaitée lorsque les opérandes sont de types non liés (pas d'héritage).Sommes-nous déjà là? Pas assez. Combien de numéros uniques avons-nous?
Les ensembles utilisent les hachages des objets et, par défaut, Python renvoie le hachage de l'identifiant de l'objet. Essayons de le remplacer:
Le résultat final ressemble à ceci (j'ai ajouté quelques assertions à la fin pour la validation):
la source
hash(tuple(sorted(self.__dict__.items())))
ne fonctionnera pas s'il y a des objets non hachables parmi les valeurs de laself.__dict__
(c'est-à-dire si l'un des attributs de l'objet est défini, disons, sur alist
).__ne__
using==
au lieu de__eq__
.__ne__
: "Par défaut,__ne__()
délègue__eq__()
et inverse le résultat à moins qu'il ne le soitNotImplemented
". 2. Si l' on veut encore mettre en œuvre__ne__
, une application plus générique (celui utilisé par Python 3 je pense) est la suivante :x = self.__eq__(other); if x is NotImplemented: return x; else: return not x
. 3. Les données__eq__
et les__ne__
implémentations sont sous-optimales:if isinstance(other, type(self)):
donne 22__eq__
et 10__ne__
appels, tandisif isinstance(self, type(other)):
que donnerait 16__eq__
et 6__ne__
appels.Vous devez être prudent avec l'héritage:
Vérifiez les types plus strictement, comme ceci:
En plus de cela, votre approche fonctionnera bien, c'est à cela que servent les méthodes spéciales.
la source
NotImplemented
comme vous le suggérez provoquera toujourssuperclass.__eq__(subclass)
, ce qui est le comportement souhaité.if other is self
. Cela évite la comparaison plus longue du dictionnaire et peut être une énorme économie lorsque les objets sont utilisés comme clés de dictionnaire.__hash__()
La façon dont vous décrivez est la façon dont je l'ai toujours fait. Puisqu'il est totalement générique, vous pouvez toujours diviser cette fonctionnalité en une classe mixin et l'hériter dans les classes où vous voulez cette fonctionnalité.
la source
other
appartient à une sous-classe deself.__class__
.__dict__
comparaison est de savoir si vous avez un attribut que vous ne souhaitez pas prendre en compte dans votre définition de l'égalité (par exemple, un identifiant d'objet unique ou des métadonnées comme un horodatage créé).Pas une réponse directe, mais semblait suffisamment pertinente pour être abordée car elle économise un peu d'ennui verbeux à l'occasion. Couper directement à partir des documents ...
functools.total_ordering (cls)
Étant donné une classe définissant une ou plusieurs méthodes de commande de comparaison riches, ce décorateur de classe fournit le reste. Cela simplifie l'effort impliqué dans la spécification de toutes les opérations de comparaison riches possibles:
La classe doit définir l' un des
__lt__()
,__le__()
,__gt__()
ou__ge__()
. De plus, la classe doit fournir une__eq__()
méthode.Nouveau dans la version 2.7
la source
Vous n'avez pas besoin de remplacer les deux
__eq__
et__ne__
vous ne pouvez les remplacer que__cmp__
mais cela aura une implication sur le résultat de ==,! ==, <,> et ainsi de suite.is
tests d'identité de l'objet. Cela signifie queis
b seraTrue
dans le cas où a et b détiennent tous deux la référence au même objet. En python, vous détenez toujours une référence à un objet dans une variable et non l'objet réel, donc pour que a soit b vrai, les objets qu'ils contiennent doivent être situés dans le même emplacement de mémoire. Comment et, surtout, pourquoi voudriez-vous remplacer ce comportement?Edit: je ne savais pas qu'il
__cmp__
avait été supprimé de python 3, alors évitez-le.la source
De cette réponse: https://stackoverflow.com/a/30676267/541136 J'ai démontré que, bien qu'il soit correct de définir
__ne__
en termes__eq__
- au lieu deTu devrais utiliser:
la source
Je pense que les deux termes que vous recherchez sont égalité (==) et identité (is). Par exemple:
la source
Le test «is» testera l'identité en utilisant la fonction intégrée «id ()» qui renvoie essentiellement l'adresse mémoire de l'objet et n'est donc pas surchargeable.
Cependant, dans le cas d'un test d'égalité d'une classe, vous voudrez probablement être un peu plus strict sur vos tests et ne comparer que les attributs de données de votre classe:
Ce code ne comparera que les données non fonctionnelles des membres de votre classe et ignorera tout ce qui est privé, ce qui est généralement ce que vous voulez. Dans le cas des objets Plain Old Python, j'ai une classe de base qui implémente __init__, __str__, __repr__ et __eq__, donc mes objets POPO ne supportent pas le poids de toute cette logique supplémentaire (et dans la plupart des cas identique).
la source
__eq__
sera déclaré enCommonEqualityMixin
(voir l'autre réponse). J'ai trouvé cela particulièrement utile lors de la comparaison d'instances de classes dérivées de Base dans SQLAlchemy. Pour ne pas comparer,_sa_instance_state
je suis passékey.startswith("__")):
àkey.startswith("_")):
. J'avais aussi quelques références en eux et la réponse d'Algorias a généré une récursion sans fin. J'ai donc nommé toutes les références arrières en commençant par'_'
afin qu'elles soient également ignorées lors de la comparaison. REMARQUE: dans Python 3.x, changeziteritems()
enitems()
.__dict__
une instance n'a rien qui commence par,__
sauf si elle a été définie par l'utilisateur. Des choses comme__class__
,__init__
etc. ne sont pas dans l'instance__dict__
, mais plutôt dans sa classe__dict__
. OTOH, les attributs privés peuvent facilement commencer__
et devraient probablement être utilisés pour__eq__
. Pouvez-vous clarifier ce que vous tentiez exactement d'éviter en sautant les__
attributs préfixés?Au lieu d'utiliser des sous-classes / mixins, j'aime utiliser un décorateur de classe générique
Usage:
la source
Cela incorpore les commentaires sur la réponse d'Algorias et compare les objets par un seul attribut parce que je ne me soucie pas du dict entier.
hasattr(other, "id")
doit être vrai, mais je sais que c'est parce que je l'ai mis dans le constructeur.la source