Python fin dalle prime versioni ha permesso di confrontare oggetti tra loro. Per descrivere cosa significa "maggiore" o "uguale" per i tuoi oggetti, sono stati creati metodi di confronto speciali (eq, ne, lt, le, gt, ge). Con l'introduzione della PEP 207 (Python 2.1+) è stato implementato un protocollo di confronto unificato, in modo che i programmatori potessero controllare esplicitamente il comportamento durante i confronti.
I tipi standard (int, str) vengono confrontati "per valore", ma le classi personalizzate per impostazione predefinita vengono confrontate "per identità" (is). Se non si implementano o si implementano in modo errato i metodi magici di confronto, il confronto dei tuoi oggetti darà un comportamento inaspettato durante il lavoro con collezioni, ordinamenti, utilizzo di set e dict, e a volte porterà a errori di tipo.
È necessario definire esplicitamente i metodi di confronto nella tua classe. Di solito si implementa eq (uguaglianza) e lt (minore), mentre gli altri metodi possono essere ottenuti automaticamente tramite il decoratore functools.total_ordering.
Esempio di codice:
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) # Utilizzo: p1 = Point(1, 2) p2 = Point(1, 3) print(p1 < p2) # True print(p1 == p2) # False
Caratteristiche chiave:
È possibile implementare solo un metodo di confronto (eq o lt) e tutto funzionerà?
No. Implementando solo eq, garantirai un confronto corretto per uguaglianza, ma gli operatori <, >, <=, >= si comporteranno in modo standard (solitamente attraverso il confronto delle identità degli oggetti). Per un comportamento completo, è necessario implementare un insieme sufficiente di metodi o avvalersi di functools.total_ordering.
Perché compare(x, y) non esiste in Python, come in altri linguaggi?
Python ha ereditato l'idea di metodi "magici" speciali per ciascun operatore, e la funzione cmp è stata rimossa a partire da Python 3. Per il confronto generale degli oggetti si utilizzano metodi di confronto ricchi (lt, eq e altro), mentre per l'ordinamento si usa il parametro key nelle funzioni sort/sorted.
Cosa succede se si confronta un oggetto con un tipo per cui non è stato implementato il metodo necessario?
Se in eq o in altri metodi si restituisce NotImplemented per i tipi non supportati, Python proverà automaticamente il metodo inverso sul secondo operando o restituirà False. Se non si gestisce un tale caso, può verificarsi un TypeError o un comportamento del tutto errato.
Esempio di codice:
class Foo: def __eq__(self, other): return NotImplemented class Bar: pass print(Foo() == Bar()) # False (prova tramite Bar.__eq__)
Uno sviluppatore ha creato una classe User e ha implementato solo eq senza hash, affinché gli utenti vengano considerati uguali per email. Poi ha provato a utilizzare oggetti User in set/come chiavi del dizionario.
Pro:
Contro:
Un programmatore utilizza @total_ordering e implementa eq e lt con controllo del tipo, e aggiunge hash se la classe è immutabile. Gli oggetti vengono confrontati correttamente, ordinati, funzionano come chiavi del dizionario.
Pro:
Contro: