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__
. On dit que les surcharges de comparaison riches sont 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 en implémenter plusieurs avec une logique presque identique. Cependant, si vous pouvez utiliser l'ordre intégré cmp
et le tri des tuples , cela __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 permuté» / comportement reflété, mais cela entraîne une augmentation nette de la complication, à mon humble avis.)
Y a-t-il des écueils imprévus dont je dois être informé si je ne fais que surcharger __cmp__
?
Je comprends la <
, <=
, ==
, etc. opérateurs peuvent être surchargées à d' autres fins, et peut retourner tout objet qu'ils aiment. Je ne m'interroge pas 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 l'a souligné Christopher , cmp
disparaît dans 3.x. Existe-t-il des alternatives qui facilitent la mise en œuvre des comparaisons __cmp__
?
la source
Réponses:
Oui, il est facile de tout implémenter, 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:
Maintenant, votre classe peut définir juste
__lt__
et multiplier l'héritage de ComparableMixin (après toutes les autres bases dont elle 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écore (le résultat pourrait être microscopiquement plus rapide à l'exécution, à un coût tout aussi infime en termes de mémoire).Bien sûr, si votre classe a un moyen particulièrement rapide de mettre en œuvre (par exemple)
__eq__
et__ne__
, elle doit les définir directement afin que les versions du mixin ne soient pas utilisées (par exemple, c'est le cas pourdict
) - en fait__ne__
pourrait bien être défini pour faciliter que comme:mais dans le code ci-dessus je voulais garder la symétrie agréable de n'utiliser que
<
;-). Quant à savoir pourquoi__cmp__
fallait aller, puisque nous fait avoir__lt__
et les amis, pourquoi garder une autre, autre façon de faire exactement la même chose autour? C'est tellement de poids mort dans chaque runtime Python (Classic, Jython, IronPython, PyPy, ...). Le code qui n'aura certainement pas de bogues est le code qui n'existe pas - d'où le principe de Python selon lequel il devrait idéalement y avoir 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 sortons de notre façon d'interdire les choses (par exemple, quasi-équivalence entre mixins et décorateurs de classe pour certains usages), mais il a vraiment fait dire que nous ne sommes pas comme à transporter le code dans les compilateurs et / ou des environnements d'exécution qui existent de manière redondante juste pour prendre en charge plusieurs approches équivalentes pour effectuer exactement la même tâche.
Modification supplémentaire: il existe en fait un moyen encore meilleur de fournir une comparaison ET un hachage pour de nombreuses classes, y compris cela dans la question - une
__key__
méthode, comme je l'ai mentionné dans mon commentaire à la question. Comme je n'ai jamais eu le temps d'écrire le PEP pour cela, vous devez actuellement l'implémenter avec un Mixin (& c) si vous l'aimez:Il est très courant que les comparaisons d'une instance avec d'autres instances se résument à comparer un tuple pour chacune avec quelques champs - et ensuite, le hachage doit être implémenté exactement sur la même base. La
__key__
méthode spéciale répond directement aux besoins.la source
TypeError: Cannot create a consistent method resolution order (MRO) for bases object, ComparableMixin
quand j'essaye ceci en Python 3. Voir le code complet sur gist.github.com/2696496functools.total_ordering
au lieu de créer le vôtreComparableMixim
. Comme suggéré dans la réponse de jmagnusson<
pour implémenter__eq__
dans Python 3 est une très mauvaise idée, à cause deTypeError: unorderable types
.Pour simplifier ce cas, il existe un décorateur de classe en Python 2.7 + / 3.2 +, functools.total_ordering , qui peut être utilisé pour implémenter ce qu'Alex suggère. Exemple tiré de la documentation:
la source
total_ordering
ne met pas en œuvre__ne__
cependant, alors attention!__ne__
. mais c'est parce__ne__
que l'implémentation par défaut est déléguée à__eq__
. Il n'y a donc rien à surveiller ici.Ceci est couvert par la PEP 207 - Comparaisons riches
De plus,
__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 il EST sur http://docs.python.org/2.7/reference/datamodel.html )la source
(Modifié le 17/06/2017 pour tenir compte des commentaires.)
J'ai essayé la réponse mixin comparable ci-dessus. J'ai rencontré des problèmes avec "Aucun". Voici une version modifiée qui gère les comparaisons d'égalité avec "Aucun". (Je ne voyais aucune raison de s'embêter avec les comparaisons d'inégalités avec None comme manquant de sémantique):
la source
self
pourrait être le singletonNone
deNoneType
et en même temps mettre en œuvre votreComparableMixin
? Et en effet cette recette est mauvaise pour Python 3.self
sera jamais êtreNone
, de sorte que la branche peut aller tout. N'utilisez pastype(other) == type(None)
; utilisez simplementother is None
. Plutôt que de -boîtier spécialNone
, test si l'autre type est une instance du type deself
et retourner leNotImplemented
singleton sinon:if not isinstance(other, type(self)): return NotImplemented
. Faites ceci pour toutes les méthodes. Python pourra alors donner à l'autre opérande une chance de fournir une réponse à la place.Inspiré par les
ComparableMixin
&KeyedMixin
réponses d'Alex Martelli , je suis venu avec le mixin suivant. Il vous permet d'implémenter une_compare_to()
méthode unique , 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 deother
. (Notez que ce mixin n'aide pas beaucoup pour les objets qui peuvent être testés pour l'égalité mais pas pour l'ordre).la source