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 NotImplemented
dal momento che i tipi confrontati sono diversi. Considera un altro esempio in cui due oggetti con tipi diversi vengono confrontati in questo modo, come 1
e '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 è
- Innanzitutto,
(1).__eq__('a')
viene provato, il che ritorna NotImplemented
. Ciò indica che l'operazione non è supportata, quindi
'a'.__eq__(1)
viene chiamato, che restituisce anche lo stesso NotImplemented
. Così,
- Gli oggetti vengono trattati come se non fossero gli stessi e
False
vengono 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 NotImplemented
realtà è 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 False
o 0
rispettivamente.
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 NoneType
classe (ma lo stesso oggetto può avere un numero qualsiasi di riferimenti). Le linee guida PEP8 lo rendono esplicito:
I confronti con i singleton come None
dovrebbero sempre essere fatti con is
o
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 is
funzionerà bene.