Home > Mobile >  What happens "behind the scenes" if I call `None == x` in Python?
What happens "behind the scenes" if I call `None == x` in Python?

Time:12-09

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 not NotImplemented 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 not NotImplemented 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.

  • Related