I search the answer why a is None works faster than a == None. I measure time by using this code:
>>> timeit.timeit("1 is None", number=10000000)
0.4035069934390217
>>> timeit.timeit("1 == None", number=10000000)
0.8190256083633187
The documentation says that a is b has function equialent is_(a, b) and that a == b has function equivalent eq(a, b). So, why is the is_ function faster than eq?
I read in some articles that is_() compares only the identifiers of objects, and eq() does "deep comparison". But I can't find this info in documentation. Does this information correct? Where I can read more about this?
Testing for identity (is) is as simple pointer comparison (are the two values the same object).
Testing for equality needs to do more work than that; for lists for example it does need to test for equality of each and every element in both lists until either something tests negative for equality or all elements in the shortest list have been tested.
Note that the two operators are quite different in what they test:
>>> lsta = []
>>> lstb = lsta
>>> lsta is listb
True
>>> lstc = []
>>> lsta is listc
False
>>> lsta == listc
True
Just because two objects are equal does not mean that they are the same object; is tests for the latter.
Here is the Python source code implementing comparisons (is, ==, <=, etc.):
Python/ceval.c:4501
static PyObject *
cmp_outcome(int op, register PyObject *v, register PyObject *w)
{
int res = 0;
switch (op) {
case PyCmp_IS:
res = (v == w);
break;
...
default:
return PyObject_RichCompare(v, w, op);
is is implemented in just one line of code, a simple C pointer comparison. Some Python primitives compare equal according to this (because of interning, or because they are singletons like True, False and None).
On the other hand, eq uses PyObject_RichCompare, which is implemented with the helper function do_richcompare:
richcmpfunc f;
PyObject *res;
int checked_reverse_op = 0;
if (v->ob_type != w->ob_type &&
PyType_IsSubtype(w->ob_type, v->ob_type) &&
(f = w->ob_type->tp_richcompare) != NULL) {
checked_reverse_op = 1;
res = (*f)(w, v, _Py_SwappedOp[op]);
if (res != Py_NotImplemented)
return res;
Py_DECREF(res);
}
if ((f = v->ob_type->tp_richcompare) != NULL) {
res = (*f)(v, w, op);
if (res != Py_NotImplemented)
return res;
Py_DECREF(res);
}
if (!checked_reverse_op && (f = w->ob_type->tp_richcompare) != NULL) {
res = (*f)(w, v, _Py_SwappedOp[op]);
if (res != Py_NotImplemented)
return res;
Py_DECREF(res);
}
This checks the argument types and potentially tries multiple comparison functions (__eq__ methods) before it can determine the answer. The comparison methods might do unlimited work (e.g. list.__eq__ has to check each element of the lists, possibly recursively), but even in the simple case of x == None, the type checks and all the extra work will amount to a real slowdown compared to is.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With