Python is not Operator: Identity vs Equality
python is not operator: Understand Python's `is not` operator for identity checks, its difference from `!=`, and when to use it correctly with None and singletons.
The python is not operator is a single operator that checks whether two references point to different objects in memory. It is not a negated version of !=; rather, it combines the identity operator is with not to test for non-identity. 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 using the __ne__ method, which can be overridden by a class to define custom equality semantics. In contrast, is not bypasses any custom comparison logic and directly compares memory addresses. For most built-in types, != performs a deep or element-wise comparison, which can be expensive for large containers. is not is always a constant-time pointer 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) # True? Actually False, because values are equal print(p1 is not p2) # True, because they are different objects
Here, p1 != p2 is False because the custom __eq__ considers them equal based on the name. However, p1 is not p2 is True because they are separate instances. Using is not when you actually 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 for checking against None. None is a singleton in Python; there is exactly one None object in memory. Therefore, identity comparison is both safe and idiomatic for None checks.
def process(data): if data is not None: # Process the data return data.upper() return "No data"
Using is not None is the standard pattern. It is faster than != None because it avoids calling __ne__, 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
Python interns certain objects for performance reasons. 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 interned c = 257 d = 257 print(c is d) # False, because 257 is not interned
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: while some strings are interned, 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 are never automatically shared unless you explicitly assign the same reference. A common mistake is assuming that two separately created objects with identical contents are the same object. This leads to incorrect is not results.
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 equality, not value inequality.
Another pitfall arises with function arguments. When you pass a mutable object to a function and modify it in place, the original reference is unchanged. Comparing with is not inside the function can be misleading if you expect it to reflect value changes.
Performance and Runtime Cost of Identity Checks
Identity checks are extremely fast because they only compare memory addresses. 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.
There is no benchmark data here because the actual cost depends on the types and the complexity of their equality methods. The key point is that is not is a pointer 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, implement __ne__ properly and use !=. Mixing the two can lead to subtle bugs that are hard to trace.
In summary, the python 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.