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