Python, dovrei implementare __ne __ operatore () sulla base di __eq__?
-
08-10-2019 - |
Domanda
Ho una classe dove voglio ignorare l'operatore __eq__()
. Sembra avere un senso che avrei dovuto ignorare l'operatore __ne__()
pure, ma ha senso implementare __ne__
sulla base di __eq__
come tale?
class A:
def __eq__(self, other):
return self.value == other.value
def __ne__(self, other):
return not self.__eq__(other)
O c'è qualcosa che mi manca con il modo in cui Python utilizza questi operatori che rende questo non è una buona idea?
Soluzione
Sì, questo è perfettamente bene. In realtà, la documentazione spinge a definire __ne__
quando si definiscono __eq__
:
Non ci sono relazioni implicite tra gli operatori di confronto. Il verità di
x==y
non implica chex!=y
è falso. Di conseguenza, nel definire__eq__()
, si dovrebbe anche definire__ne__()
in modo che gli operatori si comporteranno come previsto.
In molti casi (come questo), sarà semplice come negare il risultato di __eq__
, ma non sempre.
Altri suggerimenti
Python, dovrei implementare operatore
__ne__()
sulla base di__eq__
?
Risposta breve: No. Uso ==
al posto del __eq__
In Python 3, !=
è la negazione della ==
di default, quindi non si è nemmeno richiesto di scrivere un __ne__
, e la documentazione non è più supponente sulla scrittura uno.
In linea generale, per Python 3-solo il codice, non scrivere uno a meno è necessario passare in secondo piano l'attuazione genitore, per esempio per un oggetto incorporato.
Cioè, tenere a mente di Raymond Hettinger commento :
Il metodo
__ne__
segue automaticamente da__eq__
solo se__ne__
non è già definito in una superclasse. Quindi, se siete che eredita da un comando incorporato, è meglio ignorare sia.
Se è necessario il codice a lavorare in Python 2, seguire la raccomandazione per Python 2 e funzionerà in Python 3 che bene.
In Python 2, Python per sé non implementa automaticamente qualsiasi operazione in termini di un'altra - di conseguenza, è necessario definire il __ne__
in termini di ==
al posto del __eq__
.
PER ESEMPIO.
class A(object):
def __eq__(self, other):
return self.value == other.value
def __ne__(self, other):
return not self == other # NOT `return not self.__eq__(other)`
vedere la prova che
- operatore
__ne__()
attuazione sulla base di__eq__
e - non attuazione
__ne__
in Python 2 a tutti
fornisce comportamenti scorretti alla manifestazione di seguito.
Long risposta ??h2>
Il documentazione per Python 2 dice:
Non ci sono relazioni implicite tra gli operatori di confronto. Il verità di
x==y
non implica chex!=y
è falso. Di conseguenza, quando definire__eq__()
, si dovrebbe anche definire__ne__()
modo che il gli operatori si comporteranno come previsto.
Quindi, questo significa che se definiamo __ne__
in termini di l'inverso di __eq__
, siamo in grado di ottenere un comportamento coerente.
Questa sezione della documentazione è stata aggiornata per Python 3:
Per impostazione predefinita, i delegati
__ne__()
a__eq__()
e inverte il risultato a meno che siaNotImplemented
.
e nel "novità" sezione , vediamo questo comportamento è cambiato:
!=
ora restituisce l'opposto di==
, a meno che i rendimenti==
NotImplemented
.
Per l'implementazione __ne__
, preferiamo utilizzare l'operatore ==
invece di utilizzare il metodo __eq__
direttamente in modo che se self.__eq__(other)
sottoclasse rendimenti NotImplemented
per il tipo controllato, Python opportunamente controllare other.__eq__(self)
Dalla documentazione :
L'oggetto NotImplemented
Questo tipo ha un singolo valore. C'è un unico oggetto con questo valore. Questo oggetto è accessibile attraverso il built-in nome
NotImplemented
. metodi numerici e ricchi metodi di confronto può restituire questo valore se non attuare l'operazione per gli operandi fornito. (L'interprete tenterà quindi l'operazione riflessa, o qualche altro ripiego, a seconda dell'operatore). Il suo valore di verità è vero.
Quando dato un ricco operatore di confronto,se non sono dello stesso tipo, controlla se il Python other
è un sottotipo, e se ha tale operatore definito, si usa il metodo del other
prima (inversa per <
, <=
, >=
e >
). Se viene restituito NotImplemented
, poi si utilizza il metodo del contrario. (Fa non per controllare lo stesso metodo per due volte.) Utilizzando l'operatore ==
consente questa logica avvenire.
Aspettative
semantico, è necessario implementare __ne__
in termini di controllo per l'uguaglianza, perché gli utenti della vostra classe si aspettano che le seguenti funzioni siano equivalenti per tutte le istanze di A:.
def negation_of_equals(inst1, inst2):
"""always should return same as not_equals(inst1, inst2)"""
return not inst1 == inst2
def not_equals(inst1, inst2):
"""always should return same as negation_of_equals(inst1, inst2)"""
return inst1 != inst2
Cioè, entrambe le funzioni di cui sopra dovrebbe sempre restituire lo stesso risultato. Ma questo dipende dal programmatore.
Dimostrazione di un comportamento imprevisto quando si definisce __ne__
sulla base di __eq__
:
In primo luogo la messa a punto:
class BaseEquatable(object):
def __init__(self, x):
self.x = x
def __eq__(self, other):
return isinstance(other, BaseEquatable) and self.x == other.x
class ComparableWrong(BaseEquatable):
def __ne__(self, other):
return not self.__eq__(other)
class ComparableRight(BaseEquatable):
def __ne__(self, other):
return not self == other
class EqMixin(object):
def __eq__(self, other):
"""override Base __eq__ & bounce to other for __eq__, e.g.
if issubclass(type(self), type(other)): # True in this example
"""
return NotImplemented
class ChildComparableWrong(EqMixin, ComparableWrong):
"""__ne__ the wrong way (__eq__ directly)"""
class ChildComparableRight(EqMixin, ComparableRight):
"""__ne__ the right way (uses ==)"""
class ChildComparablePy3(EqMixin, BaseEquatable):
"""No __ne__, only right in Python 3."""
istanziare casi non equivalenti:
right1, right2 = ComparableRight(1), ChildComparableRight(2)
wrong1, wrong2 = ComparableWrong(1), ChildComparableWrong(2)
right_py3_1, right_py3_2 = BaseEquatable(1), ChildComparablePy3(2)
Comportamento atteso:
(Nota: mentre ogni seconda affermazione di ciascuno dei sotto è equivalente, e quindi logicamente ridondante a quello prima di esso, sto includendoli di dimostrare che ordine non importa quando si è una sottoclasse degli altri . )
Queste istanze hanno __ne__
implementato con ==
:
assert not right1 == right2
assert not right2 == right1
assert right1 != right2
assert right2 != right1
Queste istanze, test in Python 3, funziona anche in modo corretto:
assert not right_py3_1 == right_py3_2
assert not right_py3_2 == right_py3_1
assert right_py3_1 != right_py3_2
assert right_py3_2 != right_py3_1
E ricordare che questi sono __ne__
implementato con __eq__
- mentre questo è il comportamento previsto, l'attuazione non è corretto:
assert not wrong1 == wrong2 # These are contradicted by the
assert not wrong2 == wrong1 # below unexpected behavior!
comportamento imprevisto:
Si noti che questo confronto contraddice i confronti sopra (not wrong1 == wrong2
).
>>> assert wrong1 != wrong2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
e
>>> assert wrong2 != wrong1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
Non saltare __ne__
in Python 2
Per la prova che non si deve ignorare __ne__
implementazione in Python 2, vedere questi oggetti equivalenti:
>>> right_py3_1, right_py3_1child = BaseEquatable(1), ChildComparablePy3(1)
>>> right_py3_1 != right_py3_1child # as evaluated in Python 2!
True
Il risultato di cui sopra dovrebbe essere False
!
Python 3 fonte
L'implementazione di default per CPython __ne__
è in typeobject.c
in object_richcompare
:
case Py_NE:
/* By default, __ne__() delegates to __eq__() and inverts the result,
unless the latter returns NotImplemented. */
if (self->ob_type->tp_richcompare == NULL) {
res = Py_NotImplemented;
Py_INCREF(res);
break;
}
res = (*self->ob_type->tp_richcompare)(self, other, Py_EQ);
if (res != NULL && res != Py_NotImplemented) {
int ok = PyObject_IsTrue(res);
Py_DECREF(res);
if (ok < 0)
res = NULL;
else {
if (ok)
res = Py_False;
else
res = Py_True;
Py_INCREF(res);
}
}
Qui vediamo
Ma l'uso di default __ne__
__eq__
?
dettaglio di implementazione predefinita __ne__
di Python 3 per gli usi di livello C __eq__
perché il ==
livello più alto ( PyObject_RichCompare ) sarebbe meno efficiente -. e quindi deve anche gestire NotImplemented
Se __eq__
è implementato correttamente, quindi la negazione della ==
è anche corretto -. E permette di evitare a basso livello di dettagli di implementazione nel nostro __ne__
ci Usando ==
permette di mantenere la nostra logica di basso livello in una luogo, e Evitare di indirizzamento NotImplemented
in __ne__
.
Si potrebbe supporre erroneamente che ==
può restituire NotImplemented
.
E 'in realtà usa la stessa logica l'implementazione predefinita di __eq__
, che i controlli di identità (vedi do_richcompare e la nostra prova qui di seguito)
class Foo:
def __ne__(self, other):
return NotImplemented
__eq__ = __ne__
f = Foo()
f2 = Foo()
e il confronto:
>>> f == f
True
>>> f != f
False
>>> f2 == f
False
>>> f2 != f
True
Prestazioni
Non prendere la mia parola per esso, vediamo cosa c'è di più performante:
class CLevel:
"Use default logic programmed in C"
class HighLevelPython:
def __ne__(self, other):
return not self == other
class LowLevelPython:
def __ne__(self, other):
equal = self.__eq__(other)
if equal is NotImplemented:
return NotImplemented
return not equal
def c_level():
cl = CLevel()
return lambda: cl != cl
def high_level_python():
hlp = HighLevelPython()
return lambda: hlp != hlp
def low_level_python():
llp = LowLevelPython()
return lambda: llp != llp
Credo che questi numeri delle prestazioni parlano da soli:
>>> import timeit
>>> min(timeit.repeat(c_level()))
0.09377292497083545
>>> min(timeit.repeat(high_level_python()))
0.2654011140111834
>>> min(timeit.repeat(low_level_python()))
0.3378178110579029
Questo fa sEnse se si considera che low_level_python
sta facendo la logica in Python che altrimenti essere gestito a livello C.
Risposta ad alcuni critici
Un altro scrive risposto:
implementazione
not self == other
di Aaron Hall del metodo__ne__
non è corretto in quanto si può mai tornareNotImplemented
(not NotImplemented
èFalse
) e quindi il metodo__ne__
che ha la priorità non può mai cadere di nuovo sul metodo__ne__
che non ha la priorità.
Avere __ne__
mai ritorno NotImplemented
non lo rende non corretta. Invece, gestiamo priorità con NotImplemented
tramite l'assegno per l'uguaglianza con ==
. Supponendo ==
è implementato correttamente, abbiamo finito.
not self == other
usato per essere l'implementazione predefinita Python 3 del metodo__ne__
ma era un bug ed è stato corretto in Python 3.4 gennaio 2015, come ha notato ShadowRanger (vedi l'edizione # 21408).
Bene, cerchiamo di spiegare questo.
Come osservato in precedenza, Python 3 da maniglie __ne__
default prima verifica se i rendimenti self.__eq__(other)
NotImplemented
(singleton) - che dovrebbe essere controllato per con is
ed è tornato in caso affermativo, altrimenti deve restituire l'inverso. Ecco che la logica scritto come mixin classe:
class CStyle__ne__:
"""Mixin that provides __ne__ functionality equivalent to
the builtin functionality
"""
def __ne__(self, other):
equal = self.__eq__(other)
if equal is NotImplemented:
return NotImplemented
return not equal
Questo è necessario per la correttezza di livello C API Python, ed è stato introdotto in Python 3, rendendo
- metodi
__ne__
in questa patch per chiudere Problema 21408 e - i metodi
__ne__
nel follow-on di pulizia rimosso qui
ridondante. Tutti i metodi __ne__
rilevanti sono stati rimossi, compresi quelli di attuazione proprio controllo, oltre a quelli che delegato al __eq__
direttamente o tramite ==
-. E ==
era il modo più comune di fare in modo
Conclusione
Per Python 2 codice compatibile, uso ==
per implementare __ne__
. E 'di più:
- corretta ??li>
- semplice
- performante
In Python 3 solo, usare la negazione di basso livello sul piano C - lo è ancora di più semplice e performante (anche se il programmatore è responsabile della determinazione che è corretta ).
Anche in questo caso, fare non logica di scrittura a basso livello di alto livello Python.
Per la cronaca, un canonicamente corretta e croce PY2 / PY3 __ne__
portatile sarà simile:
import sys
class ...:
...
def __eq__(self, other):
...
if sys.version_info[0] == 2:
def __ne__(self, other):
equal = self.__eq__(other)
return equal if equal is NotImplemented else not equal
Questo funziona con qualsiasi __eq__
si potrebbe definire:
-
not (self == other)
differenza, non interferisce con, in alcuni casi fastidiosi / complessi che coinvolgono i confronti in cui una delle classi coinvolte non implica che il risultato della__ne__
è lo stesso come il risultato dinot
su__eq__
(ad esempio ORM di SQLAlchemy, dove sia__eq__
e ritorno__ne__
oggetti proxy speciali, nonTrue
oFalse
, e cercando dinot
il risultato di__eq__
restituirebbeFalse
, piuttosto che l'oggetto proxy corretto). - A differenza
not self.__eq__(other)
, questo correttamente delega al__ne__
dell'altra istanza, quando i rendimentiself.__eq__
NotImplemented
(not self.__eq__(other)
sarebbe sbagliato in più, perché èNotImplemented
truthy, così quando__eq__
non sapeva come eseguire il confronto,__ne__
sarebbe tornatoFalse
, il che implica che i due oggetti sono stati pari, quando in realtà l'unico oggetto ha chiesto non aveva idea, che implicherebbe un default di non uguale)
Se il __eq__
non usa NotImplemented
rendimenti, questo funziona (con in testa priva di significato), se si fa uso di NotImplemented
A volte, questo gestisce correttamente. E il pitone mezzi versione di controllo che, se la classe è import
-ed in Python 3, __ne__
viene lasciato indefinito, consentendo nativo, fallback efficiente di Python implementazione __ne__
(una versione C di cui sopra) di prendere in consegna.
Perché questo è necessario
Python sovraccarico regole
La spiegazione del motivo per cui si esegue questa operazione, invece di altre soluzioni è un po 'arcano. Python ha un paio di regole generali sulla operatori sovraccarico, e operatori di confronto in particolare:
- (si applica a tutti gli operatori) Quando si esegue
LHS OP RHS
, provaLHS.__op__(RHS)
, e se questo ritornaNotImplemented
, provareRHS.__rop__(LHS)
. Eccezione: seRHS
è una sottoclasse della classe diLHS
, poi provaRHS.__rop__(LHS)
prima . Nel caso degli operatori di confronto,__eq__
e__ne__
sono proprio "rop" s (per cui l'ordine di prova per__ne__
èLHS.__ne__(RHS)
, alloraRHS.__ne__(LHS)
, invertito seRHS
è una sottoclasse della classe diLHS
) - A parte l'idea del gestore "scambiato", non v'è alcuna relazione implicita tra gli operatori. Anche per istanza della stessa classe,
LHS.__eq__(RHS)
tornandoTrue
non implica rendimentiLHS.__ne__(RHS)
False
(infatti, gli operatori non sono nemmeno tenuti a restituire valori booleani; ORM come SQLAlchemy intenzionalmente non lo fanno, consentendo una sintassi di query più espressivo). A partire da Python 3, l'implementazione predefinita__ne__
si comporta in questo modo, ma non è contrattuale; è possibile ignorare__ne__
in modi che non sono rigidi opposti__eq__
.
Come questo vale per sovraccarico comparatori
Quindi, quando si esegue l'overload di un operatore, si hanno due lavori:
- Se si sa come implementare l'operazione da soli, farlo, utilizzando solo la vostra conoscenza di come fare il confronto (mai delegare, implicitamente o esplicitamente, verso l'altro lato del funzionamento; così facendo rischia di scorrettezza e / o di una ricorsione infinita, a seconda di come lo si fa)
- Se non sapere come implementare l'operazione da soli, sempre di ritorno
NotImplemented
, in modo Python può delegare alla realizzazione del l'altro operando
Il problema con not self.__eq__(other)
def __ne__(self, other):
return not self.__eq__(other)
mai delegati al l'altro lato (e non è corretto se __eq__
torna correttamente NotImplemented
). Quando ritorna self.__eq__(other)
NotImplemented
(che è "truthy"), è tornare in silenzio False
, quindi ritorna A() != something_A_knows_nothing_about
False
, quando avrebbe dovuto controllare se something_A_knows_nothing_about
saputo da confrontare con le istanze di A
, e se non lo fa, dovrebbe avere True
tornato (dal momento che se nessuna delle due parti sa come confrontare con l'altro, che sono considerati non uguali tra loro). Se A.__eq__
è implementato in modo errato (ritornando False
anziché NotImplemented
quando non riconosce l'altro lato), allora questo è "corretta" dal punto di vista A
, tornando True
(poiché A
non pensa che sia uguale, quindi non è uguale), ma potrebbe essere sbagliata dal punto di vista something_A_knows_nothing_about
, dal momento che non si è mai nemmeno chiesto something_A_knows_nothing_about
; estremità A() != something_A_knows_nothing_about
up True
, ma something_A_knows_nothing_about != A()
potrebbero False
, o qualsiasi altro valore di ritorno.
Il problema con not self == other
def __ne__(self, other):
return not self == other
è più sottile. Sta andando essere corretto per il 99% delle classi, tra cui tutte le classi per le quali __ne__
è l'inverso logica di __eq__
. Ma pause not self == other
sia delle regole di cui sopra, che i mezzi per le classi dove __ne__
non è l'inverso logico __eq__
, i risultati sono ancora una volta non riflessiva, perché uno degli operandi è mai chiesto se si può implementare __ne__
a tutti, anche se l'altro operando non può. L'esempio più semplice è una classe strano che restituisce False
per tutti i confronti, in modo da A() == Incomparable()
e A() != Incomparable()
entrambi False
ritorno. Con una corretta applicazione di A.__ne__
(quella che restituisce NotImplemented
quando non sa come fare il paragone), la relazione è riflessiva; A() != Incomparable()
e Incomparable() != A()
concordano sul risultato (perché nel primo caso, ritorna A.__ne__
NotImplemented
, ritorna quindi Incomparable.__ne__
False
, mentre nel secondo, ritorna Incomparable.__ne__
False
direttamente). Ma quando A.__ne__
è implementato come return not self == other
, ritorna A() != Incomparable()
True
(perché i rendimenti A.__eq__
, non NotImplemented
, poi ritorna Incomparable.__eq__
False
, ed inverte A.__ne__
che a True
), mentre i rendimenti Incomparable() != A()
False.
È possibile vedere un esempio di questo in azione qui .
Ovviamente, una classe che restituisce sempre False
sia per __eq__
e __ne__
è un po 'strano. Ma, come accennato prima, __eq__
e __ne__
non hanno nemmeno bisogno di tornare True
/ False
; lo SQLAlchemy ORM ha classi con comparatori che restituisce un oggetto speciale proxy per la creazione di query, non True
/ False
a tutti (loro sono "truthy" se valutati in un contesto booleano, ma sono mai dovrebbero essere valutate in un contesto del genere ).
Non avendo sovraccaricare __ne__
correttamente, classi rottura di quel genere, come il codice:
results = session.query(MyTable).filter(MyTable.fieldname != MyClassWithBadNE())
funzionerà (supponendo SQLAlchemy sa come inserire MyClassWithBadNE
in una stringa SQL a tutti, questo può essere fatto con adattatori tipo senza MyClassWithBadNE
dover cooperare a tutti), passando l'oggetto proxy dovrebbe filter
, mentre:
results = session.query(MyTable).filter(MyClassWithBadNE() != MyTable.fieldname)
finirà per passare filter
un False
pianura, perché self == other
restituisce un oggetto proxy e not self == other
solo converte l'oggetto truthy proxy per False
. Si spera, filter
genera un'eccezione su manipolato argomenti non validi come False
. Mentre sono sicuro che molti sostengono che MyTable.fieldname
dovrebbe essere costantemente sul lato sinistro del confronto, i resti fatto che non v'è alcun motivo di programmazione per far rispettare questo nel caso generale, ed un corretto generica __ne__
funziona in entrambi i modi, mentre return not self == other
funziona solo in una disposizione.
Risposta breve: sì (ma leggere la documentazione per farlo bene)
L'implementazione di ShadowRanger del metodo __ne__
è quello corretto (nel senso che si comporta esattamente come l'implementazione di default Python 3):
def __ne__(self, other):
result = self.__eq__(other)
if result is not NotImplemented:
return not result
return NotImplemented
implementazione not self == other
di Aaron Hall del metodo __ne__
non è corretto in quanto si può mai tornare NotImplemented
(not NotImplemented
è False
) e quindi il metodo __ne__
che ha la priorità non può mai cadere di nuovo sul metodo __ne__
che non ha la priorità. not self == other
usato per essere l'implementazione predefinita Python 3 del metodo __ne__
ma era un bug ed è stato corretto in Python 3.4 gennaio 2015, come ha notato ShadowRanger (vedi numero # 21408 ).
L'attuazione degli operatori di confronto
Python di riferimento di per Python 3 afferma nella sua capitolo modello III dati :
object.__lt__(self, other)
object.__le__(self, other)
object.__eq__(self, other)
object.__ne__(self, other)
object.__gt__(self, other)
object.__ge__(self, other)
Questi sono i cosiddetti metodi “ricco di confronto”. la corrispondenza tra i simboli operatori e metodi è la seguente: le chiamate
x<y
x.__lt__(y)
,x<=y
chiamax.__le__(y)
,x==y
chiamax.__eq__(y)
,x!=y
chiamax.__ne__(y)
, chiamatex>y
x.__gt__(y)
ex>=y
chiamax.__ge__(y)
.Una ricca metodo di confronto può restituire il
NotImplemented
Singleton se non implementa l'operazione per una data coppia di argomenti.Non ci sono versioni scambiati-argomento di questi metodi (da usare quando l'argomento di sinistra non supporta l'operazione ma il diritto argomento fa); piuttosto,
__lt__()
e__gt__()
sono di ogni altro riflessione,__le__()
e__ge__()
sono reciprocamente di riflessione, e__eq__()
e__ne__()
sono proprio riflesso. Se gli operandi sono di diversi tipi, e giusto tipo di operando è un diretto o sottoclasse indiretta di tipo dell'operando sinistro, il metodo riflessa l'operando di destra ha la priorità, altrimenti il ??metodo del operando sinistro ha la priorità. subclassing virtuale non è considerato.
Traducendo questo in codice Python dà (usando operator_eq
per ==
, operator_ne
per !=
, operator_lt
per <
, operator_gt
per >
, operator_le
per <=
e operator_ge
per >=
):
def operator_eq(left, right):
if type(left) != type(right) and isinstance(right, type(left)):
result = right.__eq__(left)
if result is NotImplemented:
result = left.__eq__(right)
else:
result = left.__eq__(right)
if result is NotImplemented:
result = right.__eq__(left)
if result is NotImplemented:
result = left is right
return result
def operator_ne(left, right):
if type(left) != type(right) and isinstance(right, type(left)):
result = right.__ne__(left)
if result is NotImplemented:
result = left.__ne__(right)
else:
result = left.__ne__(right)
if result is NotImplemented:
result = right.__ne__(left)
if result is NotImplemented:
result = left is not right
return result
def operator_lt(left, right):
if type(left) != type(right) and isinstance(right, type(left)):
result = right.__gt__(left)
if result is NotImplemented:
result = left.__lt__(right)
else:
result = left.__lt__(right)
if result is NotImplemented:
result = right.__gt__(left)
if result is NotImplemented:
raise TypeError(f"'<' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'")
return result
def operator_gt(left, right):
if type(left) != type(right) and isinstance(right, type(left)):
result = right.__lt__(left)
if result is NotImplemented:
result = left.__gt__(right)
else:
result = left.__gt__(right)
if result is NotImplemented:
result = right.__lt__(left)
if result is NotImplemented:
raise TypeError(f"'>' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'")
return result
def operator_le(left, right):
if type(left) != type(right) and isinstance(right, type(left)):
result = right.__ge__(left)
if result is NotImplemented:
result = left.__le__(right)
else:
result = left.__le__(right)
if result is NotImplemented:
result = right.__ge__(left)
if result is NotImplemented:
raise TypeError(f"'<=' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'")
return result
def operator_ge(left, right):
if type(left) != type(right) and isinstance(right, type(left)):
result = right.__le__(left)
if result is NotImplemented:
result = left.__ge__(right)
else:
result = left.__ge__(right)
if result is NotImplemented:
result = right.__le__(left)
if result is NotImplemented:
raise TypeError(f"'>=' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'")
return result
default implementazione dei metodi di confronto
La documentazione aggiunge:
Per impostazione predefinita, i delegati
__ne__()
a__eq__()
e inverte il risultato meno che non siaNotImplemented
. Non ci sono altre implicita relazioni tra gli operatori di confronto, per esempio, la verità di(x<y or x==y)
non implicax<=y
.
L'implementazione predefinita dei metodi di confronto (__eq__
, __ne__
, __lt__
, __gt__
, __le__
e __ge__
) può quindi essere dato da:
def __eq__(self, other):
return NotImplemented
def __ne__(self, other):
result = self.__eq__(other)
if result is not NotImplemented:
return not result
return NotImplemented
def __lt__(self, other):
return NotImplemented
def __gt__(self, other):
return NotImplemented
def __le__(self, other):
return NotImplemented
def __ge__(self, other):
return NotImplemented
Quindi questa è la corretta applicazione del metodo __ne__
. E non restituisce sempre l'inverso del metodo __eq__
perché quando il metodo ritorna __eq__
NotImplemented
, la sua not NotImplemented
inverso è False
(come bool(NotImplemented)
è True
) invece del NotImplemented
desiderato.
implementazioni non corretti di __ne__
Come Aaron Hall dimostrato sopra, not self.__eq__(other)
non è l'implementazione predefinita del metodo __ne__
. . ma nemmeno not self == other
Quest'ultimo è dimostrato sotto confrontando il comportamento dell'implementazione predefinita con il comportamento di attuazione not self == other
in due casi:
- il metodo restituisce
__eq__
NotImplemented
; - il metodo
__eq__
restituisce un valore diverso daNotImplemented
.
implementazione di default
Vediamo cosa succede quando il metodo A.__ne__
utilizza l'implementazione di default e il metodo restituisce A.__eq__
NotImplemented
:
class A:
pass
class B:
def __ne__(self, other):
return "B.__ne__"
assert (A() != B()) == "B.__ne__"
-
!=
chiamaA.__ne__
. -
A.__ne__
chiamaA.__eq__
. - ritorna
A.__eq__
NotImplemented
. -
!=
chiamaB.__ne__
. - ritorna
B.__ne__
"B.__ne__"
.
Questo dimostra che quando il metodo restituisce A.__eq__
NotImplemented
, il metodo A.__ne__
ricade sul metodo B.__ne__
.
Ora vediamo cosa succede quando il metodo A.__ne__
utilizza l'implementazione di default e il metodo A.__eq__
restituisce un valore diverso da NotImplemented
:
class A:
def __eq__(self, other):
return True
class B:
def __ne__(self, other):
return "B.__ne__"
assert (A() != B()) is False
-
!=
chiamaA.__ne__
. -
A.__ne__
chiamaA.__eq__
. - ritorna
A.__eq__
True
. - ritorna
!=
not True
, che èFalse
.
Questo dimostra che in questo caso, il metodo A.__ne__
restituisce l'inversa del metodo A.__eq__
. Così le si comporta metodo __ne__
come pubblicizzato nella documentazione.
Override del implementazione predefinita del metodo A.__ne__
con la corretta attuazione di cui sopra rese gli stessi risultati.
not self == other
realizzazione
Vediamo cosa succede quando sovrascrivendo l'implementazione predefinita del metodo A.__ne__
con l'attuazione not self == other
e il metodo restituisce A.__eq__
NotImplemented
:
class A:
def __ne__(self, other):
return not self == other
class B:
def __ne__(self, other):
return "B.__ne__"
assert (A() != B()) is True
-
!=
chiamaA.__ne__
. -
A.__ne__
chiama==
. -
==
chiamaA.__eq__
. - ritorna
A.__eq__
NotImplemented
. -
==
chiamaB.__eq__
. - ritorna
B.__eq__
NotImplemented
. - ritorna
==
A() is B()
, che èFalse
. - ritorna
A.__ne__
not False
, che èTrue
.
L'implementazione predefinita del metodo __ne__
restituito "B.__ne__"
, non True
.
Ora vediamo cosa succede quando sovrascrivendo l'implementazione predefinita del metodo A.__ne__
con l'attuazione not self == other
e il metodo A.__eq__
restituisce un valore diverso da NotImplemented
:
class A:
def __eq__(self, other):
return True
def __ne__(self, other):
return not self == other
class B:
def __ne__(self, other):
return "B.__ne__"
assert (A() != B()) is False
-
!=
chiamaA.__ne__
. -
A.__ne__
chiama==
. -
==
chiamaA.__eq__
. - ritorna
A.__eq__
True
. - ritorna
A.__ne__
not True
, che èFalse
.
L'implementazione predefinita del metodo __ne__
anche restituito False
in questo caso.
Dal momento che questa implementazione non riesce a replicare il comportamento della implementazione predefinita del generacodMetodo icetagcode quando il metodo restituisce __ne__
__eq__
, non è corretto.
Se tutti __eq__
, __ne__
, __lt__
, __ge__
, __le__
, e __gt__
un senso per la classe, poi basta implementare invece __cmp__
. In caso contrario, fare come si sta facendo, a causa del bit di Daniel DiPaolo detto (mentre stavo testando che invece di guardare in su;))