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?

È stato utile?

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 che x!=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

Il documentazione per Python 2 dice:

  

Non ci sono relazioni implicite tra gli operatori di confronto. Il   verità di x==y non implica che x!=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 sia NotImplemented.

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 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à.

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

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
  • 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 di not su __eq__ (ad esempio ORM di SQLAlchemy, dove sia __eq__ e ritorno __ne__ oggetti proxy speciali, non True o False, e cercando di not il risultato di __eq__ restituirebbe False, piuttosto che l'oggetto proxy corretto).
  • A differenza not self.__eq__(other), questo correttamente delega al __ne__ dell'altra istanza, quando i rendimenti self.__eq__ NotImplemented (not self.__eq__(other) sarebbe sbagliato in più, perché è NotImplemented truthy, così quando __eq__ non sapeva come eseguire il confronto, __ne__ sarebbe tornato False, 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:

  1. (si applica a tutti gli operatori) Quando si esegue LHS OP RHS, prova LHS.__op__(RHS), e se questo ritorna NotImplemented, provare RHS.__rop__(LHS). Eccezione: se RHS è una sottoclasse della classe di LHS, poi prova RHS.__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), allora RHS.__ne__(LHS), invertito se RHS è una sottoclasse della classe di LHS)
  2. A parte l'idea del gestore "scambiato", non v'è alcuna relazione implicita tra gli operatori. Anche per istanza della stessa classe, LHS.__eq__(RHS) tornando True non implica rendimenti LHS.__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:

  1. 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)
  2. 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 chiama x.__le__(y), x==y chiama x.__eq__(y),   x!=y chiama x.__ne__(y), chiamate x>y x.__gt__(y) e x>=y   chiama x.__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 sia NotImplemented. Non ci sono altre implicita   relazioni tra gli operatori di confronto, per esempio, la verità   di (x<y or x==y) non implica x<=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 da NotImplemented.

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__"
  1. != chiama A.__ne__.
  2. A.__ne__ chiama A.__eq__.
  3. ritorna A.__eq__ NotImplemented.
  4. != chiama B.__ne__.
  5. 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
  1. != chiama A.__ne__.
  2. A.__ne__ chiama A.__eq__.
  3. ritorna A.__eq__ True.
  4. 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
  1. != chiama A.__ne__.
  2. A.__ne__ chiama ==.
  3. == chiama A.__eq__.
  4. ritorna A.__eq__ NotImplemented.
  5. == chiama B.__eq__.
  6. ritorna B.__eq__ NotImplemented.
  7. ritorna == A() is B(), che è False.
  8. 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
  1. != chiama A.__ne__.
  2. A.__ne__ chiama ==.
  3. == chiama A.__eq__.
  4. ritorna A.__eq__ True.
  5. 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;))

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top