Python is Operator: Identity vs Equality
Learn how Python's `is` operator checks object identity, when to use it instead of `==`, and common pitfalls with integer interning, string caching, and singletons.
Python's is operator compares object identity, not value. Two variables are is-equal only when they refer to the same object in memory. That is different from ==, which compares values. Mixing up the two can lead to subtle bugs, so it helps to know exactly when to use is and when to use ==.
The Difference Between is and ==
The == operator compares values by invoking the __eq__ method of the left operand, which can be overridden in custom classes. The is operator performs a direct identity check: it returns True only if both operands point to the same object. Consider this minimal example:
a = [1, 2, 3] b = [1, 2, 3] print(a == b) # True, because values are equal print(a is b) # False, because they are different list objects
Even though a and b contain the same elements, they are distinct objects. The is operator correctly reports that they are not the same. This behavior is part of Python's object model and cannot be overridden.
How Python's is Works Under the Hood
At the language level, every object has a unique identity, accessible with the built-in id() function. In CPython, the reference implementation, that identity is the object's memory address, so is is implemented as a pointer comparison. It is a constant-time operation and does not call any user-defined methods.
Because is does not rely on operator overloading, a class can define __eq__ to change how == behaves, but it cannot change is. If two variables reference the same instance, is returns True even if __eq__ would return False. This makes is predictable for identity checks, but dangerous when used for value comparison.
Common Pitfalls: Integer Interning and String Caching
CPython caches small integers in the range -5 to 256. As a result, assignments like a = 256 and b = 256 cause both variables to reference the same cached object, so a is b evaluates to True. Outside this range, integers are created on demand, and a is b can be False for equal values:
a = 256 b = 256 print(a is b) # True (cached) c = 257 d = 257 print(c is d) # False (separate objects)
This caching behavior is an implementation detail of CPython, not a guarantee of the Python language. Other implementations may behave differently.
String interning is even less predictable. CPython interns some strings, such as short identifiers and literals that look like identifiers, but this behavior is not guaranteed by the language specification. Relying on is for string comparison is fragile and can break across Python implementations or versions.
When to Use is in Practice
The main use case for is is comparing to singletons: None, True, and False. These values have only one instance in a running Python interpreter. The recommended idiom is if x is None: rather than if x == None:, because the latter can trigger unintended __eq__ calls. The same principle applies to boolean checks: if flag is True: is explicit and avoids ambiguity with truthiness.
Another legitimate use is checking whether two variables refer to the same object, which is common in caching, memoization, or data structures that rely on object identity. For example, a linked list node might compare its next attribute to a sentinel object using is to detect the end of the list.
Performance and Runtime Behavior
Because is only compares identity, it does not invoke equality logic. For objects with expensive __eq__ implementations, an identity check can avoid that overhead. However, micro-optimizations with is are rarely justified unless you are comparing to None or checking identity in a tight loop.
is is also always a constant-time operation, while == may have O(n) complexity for containers like lists or dictionaries. If you only need to know whether two variables point to the same object, is is the correct and efficient choice.
Using is with Custom Objects
You cannot change how is behaves for custom classes. Even if a class defines __eq__ to compare attributes, is still returns False for two distinct instances with identical attribute values. This is by design: identity is a fundamental property of objects, while equality is a domain-specific concept.
class Point: def __init__(self, x, y): self.x = x self.y = y def __eq__(self, other): return self.x == other.x and self.y == other.y p1 = Point(1, 2) p2 = Point(1, 2) print(p1 == p2) # True (uses __eq__) print(p1 is p2) # False (different objects)
If you need to compare values, use ==. If you need to check whether two references point to the same object, use is. Mixing them up can lead to subtle bugs, especially when objects are passed through functions or stored in collections.
Edge Cases and Gotchas
One common mistake is using is to compare strings that are not interned. For example, a = "hello" and b = "hello" may or may not be the same object depending on how the strings are created. Concatenation or formatting often produces new objects, making a is b unreliable. Always use == for string value comparison.
The is not operator is the negation of is. It is often clearer to write if x is not None: than if not (x is None):. Both are correct, but the former reads more naturally.
Finally, be cautious when using is with mutable objects that are shared across parts of your program. Identity checks depend on the current references, not on the contents of the objects. If you need to confirm that two references point to the same object, is is the right tool; if you need to compare contents, use ==.