Python a permis de comparer des objets depuis ses premières versions. Pour décrire ce que signifie "plus grand" ou "égal" pour vos objets, des méthodes de comparaison spéciales (eq, ne, lt, le, gt, ge) ont été créées. Avec l'introduction de PEP 207 (Python 2.1+), un protocole de comparaison unique a été mis en place, permettant aux programmeurs de contrôler explicitement le comportement lors des comparaisons.
Les types standard (int, str) sont comparés "par valeur", mais les classes personnalisées sont comparées "par identité" (is) par défaut. Si les méthodes magiques de comparaison ne sont pas implémentées ou sont mal implémentées, la comparaison de vos objets peut entraîner un comportement inattendu lors de l'utilisation de collections, de tri, de set et de dict, et peut parfois conduire à des erreurs de type.
Il est nécessaire de définir explicitement les méthodes de comparaison dans votre classe. Le plus souvent, on implémente eq (égalité) et lt (inférieur), et les autres méthodes peuvent être générées automatiquement à l'aide du décorateur functools.total_ordering.
Exemple de code :
from functools import total_ordering @total_ordering class Point: def __init__(self, x, y): self.x = x self.y = y def __eq__(self, other): if not isinstance(other, Point): return NotImplemented return self.x == other.x and self.y == other.y def __lt__(self, other): if not isinstance(other, Point): return NotImplemented return (self.x, self.y) < (other.x, other.y) # Utilisation : p1 = Point(1, 2) p2 = Point(1, 3) print(p1 < p2) # True print(p1 == p2) # False
Caractéristiques clés :
Peut-on implémenter seulement une méthode de comparaison (eq ou lt) et tout fonctionnera-t-il ?
Non. En n'implémentant que eq, vous garantissez une comparaison correcte pour l'égalité, mais les opérateurs <, >, <=, >= se comporteront de manière standard (généralement via la comparaison d'identités d'objets). Pour un comportement complet, un ensemble suffisant de méthodes doit être implémenté, ou on peut utiliser functools.total_ordering.
Pourquoi compare(x, y) n'existe-t-il pas dans Python, comme dans d'autres langages ?
Python a hérité de l'idée de méthodes "magiques" spéciales pour chaque opérateur, et la fonction cmp a été supprimée à partir de Python 3. Pour comparer des objets en général, on utilise les méthodes de comparaison rich (lt, eq et autres), et pour le tri — le paramètre key dans les fonctions sort/sorted.
Que se passe-t-il si l'on compare un objet avec un type pour lequel la méthode requise n'est pas implémentée ?
Si dans eq ou d'autres méthodes on retourne NotImplemented pour des types non supportés, Python tentera automatiquement la méthode inverse sur le second opérande ou retournera False. Si ce cas n'est pas géré, une erreur de type (TypeError) peut survenir ou un comportement incorrect peut se produire.
Exemple de code :
class Foo: def __eq__(self, other): return NotImplemented class Bar: pass print(Foo() == Bar()) # False (essai via Bar.__eq__)
Le développeur a créé une classe User et n'a implémenté que eq sans hash, afin que les utilisateurs soient considérés comme égaux par email. Il a ensuite tenté d'utiliser des objets User dans un set/en tant que clés de dictionnaire.
Avantages :
Inconvénients :
Le programmeur utilise @total_ordering et implémente eq et lt avec vérification de type, et ajoute également hash si la classe est immuable. Les objets sont correctement comparés, triés et fonctionnent comme clés de dictionnaire.
Avantages :
Inconvénients :