web-dev-qa-db-fra.com

Opérateurs de comparaison vs méthodes de "comparaison riche" en Python

Quelqu'un peut-il m'expliquer les différences entre les deux. Sont-ils normalement équivalents? Peut-être que je me trompe complètement ici, mais je pensais que chaque opérateur de comparaison était nécessairement lié à une méthode «comparaison riche» . Ceci est tiré de la documentation:

La correspondance entre les symboles d'opérateur et les noms de méthodes est la suivante :

x<y appelle x.__lt__(y), x<=y appelle x.__le__(y), x==y appelle x.__eq__(y), x!=y appelle x.__ne__(y), x>y appelle x.__gt__(y) et x>=y appelle x.__ge__(y).

Voici un exemple qui démontre ma confusion.

Python 3.x:

dict1 = {1:1}
dict2 = {2:2}

>>> dict1 < dict2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'dict' and 'dict'
>>> dict1.__lt__(dict2)
NotImplemented

Python 2.x:

dict1 = {1:1}
dict2 = {2:2}

>>> dict1 < dict2
True
>>> dict1.__lt__(dict2)
NotImplemented

Dans l'exemple de Python 3, il semble logique que l'appel de dict1 < dict2 ne soit pas pris en charge. Mais qu'en est-il de l'exemple Python 2? Pourquoi est-ce accepté?

Je sais que contrairement à Python 2, dans Python 3, tous les objets ne prennent pas en charge les opérateurs de comparaison. À ma grande surprise cependant, les deux versions renvoient le singleton NotImplemented en appelant __lt__().

10
scharette

Ceci repose sur la méthode magique __cmp__, que les opérateurs de comparaison enrichie devaient remplacer:

>>> dict1 = {1:1}
>>> dict2 = {2:2}
>>> dict1.__cmp__
<method-wrapper '__cmp__' of dict object at 0x10f075398>
>>> dict1.__cmp__(dict2)
-1

En ce qui concerne la commande logic , voici le Python 2.7 documentation :

Les mappages (instances de dict) comparent égal si et seulement s'ils ont Paires égales (clé, valeur). La comparaison d'égalité des clés et des valeurs Applique la réflexivité.

Les résultats autres que l'égalité sont résolus de manière cohérente, mais ne sont pas Définis autrement.

Avec une note de bas de page:

Les versions précédentes de Python utilisaient une comparaison lexicographique des listes Triées (clé, valeur), mais cela coûtait très cher pour le cas courant de Comparaison pour l'égalité. Une version encore plus ancienne de Python comparait Dictionnaires par identité uniquement, mais cela a provoqué des surprises, car Prévoyait pouvoir tester un dictionnaire pour la vacuité en le comparant à {}.

Et, dans Python 3.0, la commande a été simplifiée. Ceci est du documentation :

Les opérateurs de comparaison de commandes (<, <=, >=, >) lèvent une exception TypeError Lorsque les opérandes n’ont pas d’ordre naturel significatif.

builtin.sorted() et list.sort() n'acceptent plus l'argument cmp Fournissant une fonction de comparaison. Utilisez l'argument clé à la place.

La fonction cmp() doit être traitée comme telle et la méthode spéciale __cmp__() N'est plus prise en charge. Utilisez __lt__() pour le tri, __eq__() avec __hash__() et d’autres comparaisons enrichies, si nécessaire. (Si vous avez vraiment besoin de la fonctionnalité cmp(), vous pouvez utiliser l'expression (a > b) - (a <> b) comme équivalent pour cmp(a, b).)

Ainsi, pour être explicite, dans Python 2, étant donné que les opérateurs de comparaison riches ne sont pas implémentés, les objets dict vont revenir à __cmp__, à partir du modèle de données documentation :

object.__cmp__(self, other)
Appelé par des opérations de comparaison si la comparaison riche (Voir ci-dessus) n'est pas définie. Doit renvoyer un entier négatif Si soi <autre, zéro si soi == autre, un entier positif Si soi> autre.

7
juanpa.arrivillaga

Note pour l'opérateur < versus __lt__:

import types

class A:
    def __lt__(self, other): return True

def new_lt(self, other): return False

a = A()
print(a < a, a.__lt__(a))  # True True
a.__lt__ = types.MethodType(new_lt, a)
print(a < a, a.__lt__(a))  # True False
A.__lt__ = types.MethodType(new_lt, A)
print(a < a, a.__lt__(a))  # False False

< appelle __lt__ défini sur la classe; __lt__ appelle __lt__ défini sur l'objet.

C'est généralement pareil :) Et c'est totalement délicieux à utiliser: A.__lt__ = new_lt

1
Ivo