Understanding the Difference Between == and is in Python 3.6
This article examines the implementation details of Python's == and is operators, showing how both are compiled to COMPARE_OP bytecode with different opcodes, exploring the underlying C functions like cmp_outcome and object_richcompare, and explaining the rich comparison protocol and its default behavior.
The article analyzes the distinction between the == and is operators in Python 3.6 by inspecting the generated bytecode and the CPython source code.
Using a simple function that assigns two identical integers and then applies == and is , the disassembled bytecode reveals that both comparisons are performed by the COMPARE_OP instruction, with opcode 2 for == and opcode 8 for is .
The COMPARE_OP opcode forwards the operation to the cmp_outcome function, which distinguishes the two cases via the oparg parameter. The relevant C snippet is:
static PyObject * cmp_outcome(int op, PyObject *v, PyObject *w) {
switch (op) {
case PyCmp_IS:
return (v == w) ? Py_True : Py_False;
case PyCmp_IS_NOT:
return (v != w) ? Py_True : Py_False;
...
}
}Both == and is are part of Python's "richcompare" mechanism, which maps the six comparison operators to special methods ( __eq__ , __lt__ , etc.) and an integer code. The type object holds a pointer tp_richcompare that determines how each object handles these operations.
The core dispatch function PyObject_RichCompare validates arguments and then calls do_richcompare . The latter implements four main cases:
If the right‑hand operand is a subclass of the left‑hand operand and defines the relevant rich‑compare method, that method is invoked first.
Otherwise, if the left‑hand operand defines the method, it is used.
If the left operand lacks the method but the right operand defines it, the right‑hand method is called with swapped operands.
If neither side provides a method, a default handling is performed, which for == and != falls back to pointer identity, while other operators raise TypeError .
Examples in the article demonstrate each case with custom classes overriding __eq__ or __ne__ , showing how the dispatch order affects the result.
The default implementation for the base object type is provided by object_richcompare :
static PyObject * object_richcompare(PyObject *self, PyObject *other, int op) {
switch (op) {
case Py_EQ:
return (self == other) ? Py_True : Py_NotImplemented;
case Py_NE:
/* delegate to __eq__ and invert */
...
default:
return Py_NotImplemented;
}
}Consequently, is always checks whether two references point to the same memory address (object identity). The == operator also compares identity by default, unless the object's type overrides tp_richcompare to provide a custom equality semantics.
The article concludes that understanding these internals clarifies why is is identity‑based, while == can be either identity‑based or value‑based depending on the type's rich‑compare implementation.
Python Programming Learning Circle
A global community of Chinese Python developers offering technical articles, columns, original video tutorials, and problem sets. Topics include web full‑stack development, web scraping, data analysis, natural language processing, image processing, machine learning, automated testing, DevOps automation, and big data.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.