Python 2.x a deux façons de surcharger les opérateurs de comparaison, __cmp__
ou les "opérateurs de comparaison riches" tels que __lt__
. Les riches surcharges de comparaison seraient préférées, mais pourquoi en est-il ainsi?
Les opérateurs de comparaison riches sont plus simples à implémenter chacun, mais vous devez implémenter plusieurs d'entre eux avec une logique presque identique. Cependant, si vous pouvez utiliser le tri intégré cmp
et le triplet, alors __cmp__
devient assez simple et remplit toutes les comparaisons:
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))
Cette simplicité semble mieux répondre à mes besoins que de surcharger les 6 (!) Des comparaisons riches. (Cependant, vous pouvez le ramener à "juste" 4 si vous vous fiez à "l'argument échangé"/au comportement réfléchi, mais cela se traduit par une nette augmentation des complications, à mon humble avis.)
Y a-t-il des pièges imprévus dont je dois être informé si je ne surcharge que __cmp__
?
Je comprends le <
, <=
, ==
, etc., les opérateurs peuvent être surchargés à d'autres fins et peuvent renvoyer tout objet qu'ils souhaitent. Je ne pose pas de question sur les mérites de cette approche, mais seulement sur les différences lors de l'utilisation de ces opérateurs pour des comparaisons dans le même sens qu'ils signifient pour les nombres.
Mise à jour: Comme Christopher l'a souligné , cmp
disparaît dans 3.x. Existe-t-il des alternatives qui facilitent la mise en œuvre des comparaisons comme ci-dessus __cmp__
?
Oui, il est facile de tout mettre en œuvre, par exemple __lt__
avec une classe mixin (ou une métaclasse, ou un décorateur de classe si votre goût fonctionne de cette façon).
Par exemple:
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
Maintenant, votre classe peut définir simplement __lt__
et multipliez héritez de ComparableMixin (après toutes les autres bases dont il a besoin, le cas échéant). Un décorateur de classe serait assez similaire, insérant simplement des fonctions similaires en tant qu'attributs de la nouvelle classe qu'il décorait (le résultat pourrait être microscopiquement plus rapide à l'exécution, à un coût tout aussi minime en termes de mémoire).
Bien sûr, si votre classe a un moyen particulièrement rapide à implémenter (par exemple) __eq__
et __ne__
, il doit les définir directement pour que les versions du mixin ne soient pas utilisées (par exemple, c'est le cas pour dict
) - en fait __ne__
pourrait bien être défini pour faciliter cela:
def __ne__(self, other):
return not self == other
mais dans le code ci-dessus, je voulais garder la symétrie agréable de n'utiliser que <
;-). Quant à savoir pourquoi __cmp__
devait partir, car nous avions __lt__
et mes amis, pourquoi garder une autre façon différente de faire exactement la même chose? C'est juste tellement de poids mort dans tous les Python runtime (Classic, Jython, IronPython, PyPy, ...). Le code qui certainement n'aura pas de bugs est le code qui n'est pas là - d'où le principe de Python selon lequel il devrait y avoir idéalement une manière évidente d'effectuer une tâche (C a le même principe dans la section "Spirit of C" de la norme ISO, btw).
Cela ne signifie pas que nous nous efforçons d'interdire les choses (par exemple, la quasi-équivalence entre les mixins et les décorateurs de classe pour certaines utilisations), mais cela signifie certainement ne signifie que nous n'aimons pas porter autour de code dans les compilateurs et/ou runtimes qui existe de manière redondante juste pour prendre en charge plusieurs approches équivalentes pour effectuer exactement la même tâche.
Édition supplémentaire: il existe en fait un meilleur moyen de fournir une comparaison ET un hachage pour de nombreuses classes, y compris dans la question - un __key__
méthode, comme je l'ai mentionné dans mon commentaire sur la question. Comme je n'ai jamais réussi à écrire le PEP pour cela, vous devez actuellement l'implémenter avec un Mixin (& c) si vous l'aimez:
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__())
Il est très courant que les comparaisons d'une instance avec d'autres instances se résument à la comparaison d'un Tuple pour chacune avec quelques champs - et ensuite, le hachage doit être implémenté exactement de la même manière. Le __key__
adresses de méthodes spéciales qui nécessitent directement.
Pour simplifier ce cas, il y a un décorateur de classe dans Python 2.7 +/3.2 +, functools.total_ordering , qui peut être utilisé pour implémenter ce que suggère Alex. Exemple tiré de la documentation :
@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()))
Ceci est couvert par PEP 207 - Comparaisons riches
Également, __cmp__
disparaît dans python 3.0. (Notez qu'il n'est pas présent sur http://docs.python.org/3.0/reference/datamodel.html mais it IS on http://docs.python.org/2.7/reference/datamodel.html )
(Modifié le 17/06/17 pour tenir compte des commentaires.)
J'ai essayé la réponse mixin comparable ci-dessus. J'ai eu des ennuis avec "Aucun". Voici une version modifiée qui gère les comparaisons d'égalité avec "Aucune". (Je n'ai vu aucune raison de s'embêter avec des comparaisons d'inégalité avec None comme manquant de sémantique):
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
Inspiré par les réponses ComparableMixin
& KeyedMixin
d'Alex Martelli, j'ai trouvé le mixin suivant. Il vous permet d'implémenter une seule méthode _compare_to()
, qui utilise des comparaisons basées sur des clés similaires à KeyedMixin
, mais permet à votre classe de choisir la clé de comparaison la plus efficace en fonction du type de other
. (Notez que ce mixage n'aide pas beaucoup pour les objets dont on peut tester l'égalité mais pas l'ordre).
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