Python od pierwszych wersji pozwalał porównywać obiekty między sobą. Aby opisać, co oznacza „większy” lub „równy” dla twoich obiektów, stworzono specjalne metody porównywania (eq, ne, lt, le, gt, ge). Wraz z pojawieniem się PEP 207 (Python 2.1+) został wdrożony jednolity protokół porównania, aby programiści mogli wyraźnie kontrolować zachowanie podczas porównań.
Typy standardowe (int, str) porównywane są „po wartości”, ale klasy użytkownika domyślnie porównywane są „po tożsamości” (is). Jeśli nie zaimplementujesz lub zaimplementujesz błędnie magiczne metody porównania, porównanie twoich obiektów da nieoczekiwane zachowanie podczas pracy z kolekcjami, sortowaniem, używaniem set i dict, a czasami doprowadzi do błędów typów.
Należy wyraźnie zdefiniować metody porównania w swojej klasie. Najczęściej implementuje się eq (równość) i lt (mniejsze), a pozostałe metody można uzyskać automatycznie przy pomocy dekoratora functools.total_ordering.
Przykład kodu:
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) # Użycie: p1 = Point(1, 2) p2 = Point(1, 3) print(p1 < p2) # True print(p1 == p2) # False
Kluczowe cechy:
Czy można zaimplementować tylko jedną metodę porównania (eq lub lt), a wszystko będzie działać?
Nie. Implementując tylko eq, zapewnisz poprawne porównanie równości, ale operatory <, >, <=, >= będą zachowywać się standardowo (zwykle poprzez porównanie tożsamości obiektów). Aby uzyskać pełne działanie, należy zaimplementować wystarczający zestaw metod lub skorzystać z functools.total_ordering.
Dlaczego compare(x, y) nie istnieje w Pythonie, jak w innych językach?
Python odziedziczył ideę specjalnych „magicznych” metod dla każdego operatora, a funkcja cmp została usunięta od Pythona 3. Do ogólnego porównania obiektów używa się metod bogatego porównania (lt, eq itd.), a do sortowania — parametru key w funkcjach sort/sorted.
Co się stanie, jeśli porównasz obiekt z typem, dla którego nie zaimplementowano potrzebnej metody?
Jeśli w eq lub innych metodach zwrócisz NotImplemented przy nieobsługiwanych typach, Python automatycznie spróbuje odwrotnej metody na drugim operandie lub zwróci False. Jeśli nie obsłużysz takiego przypadku, może wystąpić TypeError lub całkowicie błędne zachowanie.
Przykład kodu:
class Foo: def __eq__(self, other): return NotImplemented class Bar: pass print(Foo() == Bar()) # False (próba przez Bar.__eq__)
Programista stworzył klasę User i zaimplementował tylko eq bez hash, aby użytkownicy byli uważani za równych według e-maila. Następnie próbował użyć obiektów User w zbiorze/jako kluczy słownika.
Zalety:
Wady:
Programista korzysta z @total_ordering i implementuje eq oraz lt z kontrolą typu, a także dodaje hash, jeśli klasa jest niezmienna. Obiekty są poprawnie porównywane, sortowane, działają jako klucze słownika.
Zalety:
Wady: