Python `is not` Operator: Identity vs Equality
Understand Python's `is not` operator, how it differs from `!=`, and when to use it for identity checks with None and singletons.
Python's is not operator is a single operator that checks whether two references point to different objects in memory. It is the inverse of the identity operator is, not a negated version of !=. This distinction matters because identity and equality are different concepts in Python, and using the wrong one can lead to subtle bugs, especially when working with cached objects, singletons, or mutable data structures.
What is not Actually Checks
is not evaluates to True when the two operands refer to distinct objects. It does not compare the contents or values of those objects. The expression a is not b is equivalent to not (a is b), and it returns True if a and b are not the same object reference.
a = [1, 2, 3] b = [1, 2, 3] print(a is not b) # True, because a and b are separate list objects print(a != b) # False, because their values are equal
In this example, a and b hold identical values but are distinct objects. The is not operator correctly reports that they are not the same object, while != reports that their values are equal. This is the core semantic difference.
How is not Differs from !=
The != operator compares values, normally through __ne__ or the inverse of __eq__, and a class can override either method to define custom equality semantics. is not bypasses custom comparison logic and compares object identity directly. For built-in containers such as lists, dictionaries, and sets, != may perform element-wise comparisons, which can be expensive for large containers. is not is a constant-time object identity comparison.
Consider a custom class that overrides __eq__:
class Person: def __init__(self, name): self.name = name def __eq__(self, other): return isinstance(other, Person) and self.name == other.name p1 = Person('Alice') p2 = Person('Alice') print(p1 != p2) # False, because __eq__ says the values are equal print(p1 is not p2) # True, because they are different objects
Here, p1 != p2 is False because the custom __eq__ considers the two people equal by name. However, p1 is not p2 is True because they are separate instances. Using is not when you need value inequality will give the wrong result for such objects.
When to Use is not with None
The most common and recommended use of is not is checking against None. None is a singleton in Python; there is exactly one None object. Therefore, identity comparison is both safe and idiomatic for None checks.
def process(data): if data is not None: return data.upper() return 'No data'
Using is not None is the standard pattern. It is faster than != None because it avoids calling a custom equality method, and it is unambiguous because None is always the same object. The same logic applies to True and False, which are also singletons.
Identity and Interning: Why Small Integers and Strings Behave Differently
CPython interns certain objects for performance. Small integers in the range -5 to 256 are cached, and some short strings may be automatically interned. This means that two variables assigned the same small integer may actually reference the same object.
a = 256 b = 256 print(a is b) # True, because 256 is cached in CPython c = 257 d = 257 print(c is d) # False in CPython, because 257 is not cached
This behavior is an implementation detail and can vary across Python versions and interpreters. Relying on is not to compare integer values is unsafe because it may produce inconsistent results. For numeric values, always use != or ==. The same caution applies to strings: some strings are interned, but many are not, so is not is not a reliable value comparison.
Common Pitfalls with is not on Mutable Objects
Mutable objects like lists, dictionaries, and sets do not share state just because their contents are equal. A common mistake is assuming that two separately created objects with identical contents are the same object.
list1 = [1, 2, 3] list2 = [1, 2, 3] if list1 is not list2: print('Lists are different objects') # This always prints
Even though the lists have identical contents, they are distinct objects. If you intended to check whether the lists have different values, you must use !=. Using is not here only tells you about reference inequality, not value inequality.
A related rule: use is not to check whether two names refer to different objects, but use != to check whether their values differ. This distinction is especially easy to blur with mutable objects because their contents can change after creation.
Performance and Runtime Cost of Identity Checks
Identity checks are fast because they compare object identity rather than invoking arbitrary equality logic. This makes is not a good choice when you need to test for reference inequality, such as checking whether a variable is not None. The performance advantage is negligible for a single comparison, but in tight loops or hot paths, avoiding a potentially expensive __ne__ implementation can matter.
For example, if you have a custom class with a complex __eq__ that performs heavy computation, using is not to compare against a known singleton avoids that overhead. However, this only applies when you truly want identity comparison. Using is not to speed up value comparison is incorrect and will produce wrong results.
The actual cost depends on the types involved and the complexity of their equality methods. The key point is that is not is an object identity comparison, while != may invoke arbitrary Python code.
Maintainability: Choosing the Right Comparison Operator
Choosing between is not and != is a matter of correctness and clarity. Use is not when you explicitly need to check whether two references point to different objects. The most common case is x is not None. Use != when you need to compare values, regardless of object identity.
A clear rule: if you are comparing to a singleton like None, True, or False, use is or is not. For all other comparisons, use == or !=. This rule keeps code predictable and avoids relying on interning behavior that may change.
Consider the readability of code. if x is not None is immediately understood by Python developers as a null check. if x != None is also valid but is less idiomatic and may trigger linters. Sticking to the standard pattern improves maintainability because future readers will not have to guess your intent.
When working with custom classes, be explicit about what you are comparing. If you need to check that two variables are not the same instance, is not is the correct tool. If you need to check that their logical values differ, use != and make sure the class's equality methods are implemented correctly. Mixing the two can lead to subtle bugs that are hard to trace.
In summary, Python's is not operator serves a specific purpose: reference inequality. It is not a general-purpose inequality operator. By understanding its semantics, you can write code that is both correct and efficient, avoiding the common pitfalls that arise from confusing identity with equality.