Perché `if None .__ eq __ (“ a ”)` sembra valutare True (ma non del tutto)?


146

Se esegui la seguente istruzione in Python 3.7, (dal mio test) verrà stampato b:

if None.__eq__("a"):
    print("b")

Tuttavia, None.__eq__("a")valuta NotImplemented.

Naturalmente, "a".__eq__("a")valuta Truee "b".__eq__("a")valuta False.

Inizialmente l'ho scoperto durante il test del valore restituito di una funzione, ma nel secondo caso non ho restituito nulla, quindi la funzione è tornata None.

Cosa sta succedendo qui?

Risposte:


178

Questo è un ottimo esempio del perché i __dunder__metodi non dovrebbero essere usati direttamente in quanto abbastanza spesso non sono sostituti appropriati per i loro operatori equivalenti; dovresti usare ==invece l' operatore per confronti di uguaglianza, o in questo caso speciale, quando controlli None, usa is(salta in fondo alla risposta per maggiori informazioni).

Hai finito

None.__eq__('a')
# NotImplemented

Che ritorna NotImplementeddal momento che i tipi confrontati sono diversi. Considera un altro esempio in cui due oggetti con tipi diversi vengono confrontati in questo modo, come 1e 'a'. Anche fare (1).__eq__('a')non è corretto e tornerà NotImplemented. Il modo giusto di confrontare questi due valori per l'uguaglianza sarebbe

1 == 'a'
# False

Quello che succede qui è

  1. Innanzitutto, (1).__eq__('a')viene provato, il che ritorna NotImplemented. Ciò indica che l'operazione non è supportata, quindi
  2. 'a'.__eq__(1)viene chiamato, che restituisce anche lo stesso NotImplemented. Così,
  3. Gli oggetti vengono trattati come se non fossero gli stessi e Falsevengono restituiti.

Ecco un simpatico MCVE che utilizza alcune classi personalizzate per illustrare come ciò avvenga:

class A:
    def __eq__(self, other):
        print('A.__eq__')
        return NotImplemented

class B:
    def __eq__(self, other):
        print('B.__eq__')
        return NotImplemented

class C:
    def __eq__(self, other):
        print('C.__eq__')
        return True

a = A()
b = B()
c = C()

print(a == b)
# A.__eq__
# B.__eq__
# False

print(a == c)
# A.__eq__
# C.__eq__
# True

print(c == a)
# C.__eq__
# True

Ovviamente, ciò non spiega perché l'operazione ritorni vera. Questo perché in NotImplementedrealtà è un valore veritiero:

bool(None.__eq__("a"))
# True

Uguale a,

bool(NotImplemented)
# True

Per ulteriori informazioni su quali valori sono considerati verità e falsità, consultare la sezione relativa ai test sul valore della verità e questa risposta . Vale la pena notare qui che NotImplementedè vero, ma sarebbe stata una storia diversa se la classe avesse definito un metodo __bool__o __len__che fosse ritornato Falseo 0rispettivamente.


Se si desidera l'equivalente funzionale ==dell'operatore, utilizzare operator.eq:

import operator
operator.eq(1, 'a')
# False

Tuttavia, come accennato in precedenza, per questo specifico scenario , in cui si sta verificando None, utilizzare is:

var = 'a'
var is None
# False

var2 = None
var2 is None
# True

L'equivalente funzionale di questo sta usando operator.is_:

operator.is_(var2, None)
# True

Noneè un oggetto speciale ed esiste solo 1 versione in memoria in qualsiasi momento. IOW, è l'unico singleton della NoneTypeclasse (ma lo stesso oggetto può avere un numero qualsiasi di riferimenti). Le linee guida PEP8 lo rendono esplicito:

I confronti con i singleton come Nonedovrebbero sempre essere fatti con iso is not, mai, gli operatori di uguaglianza.

In sintesi, per i singleton come None, un controllo di riferimento con isè più appropriato, sebbene entrambi ==e isfunzionerà bene.


33

Il risultato che stai vedendo è causato da questo fatto

None.__eq__("a") # evaluates to NotImplemented

valuta NotImplementede NotImplementedil valore di verità è documentato come True:

https://docs.python.org/3/library/constants.html

Valore speciale che deve essere restituito dai metodi speciali binari (ad esempio __eq__(), __lt__(), __add__(), __rsub__(), etc.) per indicare che l'operazione non è implementata rispetto all'altro tipo; può essere restituito con i metodi speciali binari sul posto (ad es __imul__(). __iand__(), ecc.) per lo stesso scopo. Il suo valore di verità è vero.

Se si chiama il __eq()__metodo manualmente anziché semplicemente utilizzarlo ==, è necessario essere pronti a gestire la possibilità che possa tornare NotImplementede che il suo valore di verità sia vero.


16

Come hai già capito, None.__eq__("a")valuta NotImplementedcomunque se provi qualcosa del genere

if NotImplemented:
    print("Yes")
else:
    print("No")

il risultato è

questo significa che il valore di verità di NotImplemented true

Pertanto l'esito della domanda è ovvio:

None.__eq__(something) i rendimenti NotImplemented

E bool(NotImplemented)valuta True

Quindi if None.__eq__("a")è sempre vero


1

Perché?

Restituisce un NotImplemented, sì:

>>> None.__eq__('a')
NotImplemented
>>> 

Ma se guardi questo:

>>> bool(NotImplemented)
True
>>> 

NotImplementedè in realtà un valore veritiero, quindi è per questo che ritorna b, tutto ciò che è Truepasserà, qualsiasi cosa Falsenon lo sarebbe.

Come risolverlo?

Devi controllare se lo è True, quindi sii più sospettoso, come vedi:

>>> NotImplemented == True
False
>>> 

Quindi faresti:

>>> if None.__eq__('a') == True:
    print('b')


>>> 

E come vedi, non restituirebbe nulla.


1
risposta visivamente più chiara - v aggiunta utile - grazie
scharfmn

1
:) “Un'aggiunta utile” non cattura del tutto quello che stavo cercando di dire (come vedete) - forse “l'eccellenza tardiva” è ciò che volevo - evviva
scharfmn

@scharfmn yes? Sono curioso di sapere cosa ne pensi aggiunge questa risposta che non è già stata coperta prima.
cs95,

in qualche modo le cose visive / sostituiscono qui aggiungono chiarezza - demo completa
scharfmn

@scharfmn ... Quale risposta accettata ha anche se i prompt sono stati rimossi. Hai votato esclusivamente perché le istruzioni del terminale sono state pigramente lasciate in sospeso?
cs95,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.