I am learning and playing around with Python and I came up with the following test code (please be aware that I would not write productive code like that, but when learning new languages I like to play around with the language's corner cases):
a = None
print(None == a) # I expected True, I got True
b = 1
print(None == b) # I expected False, I got False
class MyNone:
# Called if I compare some myMyNone == somethingElse
def __eq__(self, __o: object) -> bool:
return True
c = MyNone()
print (None == c) # !!! I expected False, I got True !!!
Please see the very last line.
How can it be that None == something
, where something is clearly not None
, return True
? I would have expected that result for something == None
, but not for None == something
.
I expected that it would call None is something
behind the scenes.
So I think the question boils down to: How does the __eq__
method of the None
singleton object look like and how could I have found that out?
PS: I am aware of PEP-0008 and its quote
Comparisons to singletons like None should always be done with is or is not, never the equality operators.
but I still would like to know why print (None == c)
in the above example returns True
.
CodePudding user response:
As stated in the docs: x==y
calls x.__eq__(y)
, but None.__eq__(...)
seems to return NotImplemented
for anything except the None
itself (missing piece: why? I don't know), so Python tries the comparison the other way around and calls the __eq__
from the MyNone
which always returns True
.
CodePudding user response:
In fact, None
's type does not have its own __eq__
method; within Python we can see that it apparently inherits from the base class object
:
>>> type(None).__eq__
<slot wrapper '__eq__' of 'object' objects>
But this is not really what's going on in the source code. The implementation of None
can be found in Objects/object.c
in the CPython source, where we see:
PyTypeObject _PyNone_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"NoneType",
0,
0,
none_dealloc, /*tp_dealloc*/ /*never called*/
0, /*tp_vectorcall_offset*/
0, /*tp_getattr*/
0, /*tp_setattr*/
// ...
0, /*tp_richcompare */
// ...
0, /*tp_init */
0, /*tp_alloc */
none_new, /*tp_new */
};
I omitted most of the irrelevant parts. The important thing here is that _PyNone_Type
's tp_richcompare
is 0
, i.e. a null pointer. This is checked for in the do_richcompare
function:
if ((f = Py_TYPE(v)->tp_richcompare) != NULL) {
res = (*f)(v, w, op);
if (res != Py_NotImplemented)
return res;
Py_DECREF(res);
}
if (!checked_reverse_op && (f = Py_TYPE(w)->tp_richcompare) != NULL) {
res = (*f)(w, v, _Py_SwappedOp[op]);
if (res != Py_NotImplemented)
return res;
Py_DECREF(res);
}
Translating for those who don't speak C:
- If the left-hand-side's
tp_richcompare
function is not null, call it, and if its result is notNotImplemented
then return that result. - Otherwise if the reverse hasn't already been checked*, and the right-hand-side's
tp_richcompare
function is not null, call it, and if the result is notNotImplemented
then return that result.
There are some other branches in the code, to fall back to in case none of those branches returns a result. But these two branches are enough to see what's going on. It's not that type(None).__eq__
returns NotImplemented
, rather the type doesn't have the corresponding function in the C source code at all. That means the second branch is taken, hence the result you observe.
*The flag checked_reverse_op
is set if the reverse direction has already been checked; this happens if the right-hand-side is a strict subtype of the left-hand-side, in which case it takes priority. That doesn't apply in this case since there is no subtype relation between type(None)
and your class.