Identity Operators¶
Identity operators compare memory addresses of objects, not their values.
Mental Model
is asks "are these the exact same object in memory?" while == asks "do these objects have the same value?" Two lists with identical contents are == but not is. The only common use for is is comparing against singletons: x is None, x is True, x is False. For everything else, use ==.
| Operator | Description | Example |
|---|---|---|
is |
Same object in memory | x is y |
is not |
Different objects | x is not y |
== vs is¶
==compares values (equality)iscompares identity (same object in memory)
```python a = [1, 2, 3] b = [1, 2, 3] c = a
print(a == b) # True (same value) print(a is b) # False (different objects) print(a is c) # True (same object)
print(id(a)) # e.g., 140234567890 print(id(b)) # e.g., 140234567920 (different) print(id(c)) # e.g., 140234567890 (same as a) ```
Mutable vs Immutable Objects¶
Mutable Objects (list, dict, set)¶
Each creation produces a new object:
```python a = [1, 2, 3] b = [1, 2, 3] print(a == b) # True print(a is b) # False (different objects)
a = {1: 'one'} b = {1: 'one'} print(a == b) # True print(a is b) # False
a = {1, 2, 3} b = {1, 2, 3} print(a == b) # True print(a is b) # False ```
Immutable Objects¶
Python may reuse immutable objects (interning):
```python
Small integers are cached¶
a = 5 b = 5 print(a is b) # True (same object)
Strings may be interned¶
a = "hello" b = "hello" print(a is b) # True (typically)
Booleans are singletons¶
a = True b = True print(a is b) # True
None is a singleton¶
a = None b = None print(a is b) # True ```
Warning: Don't rely on is for immutable objects except None, True, False.
When to Use is¶
Use is for Singletons¶
```python
Checking for None¶
if x is None: print("x is None")
Checking for True/False (rare)¶
if flag is True: print("flag is exactly True") ```
Use == for Values¶
```python
Comparing values¶
if x == 5: print("x equals 5")
if name == "Alice": print("name is Alice")
if my_list == [1, 2, 3]: print("lists have same elements") ```
Mutation and Shared References¶
Separate Objects¶
Modifying one does not affect the other:
```python a = [1, 2, 3] b = [1, 2, 3] a[0] = "one"
print(a) # ['one', 2, 3] print(b) # [1, 2, 3] ```
Shared Reference¶
Both names point to the same object:
```python a = [1, 2, 3] b = a a[0] = "one"
print(a) # ['one', 2, 3] print(b) # ['one', 2, 3] (same object!) ```
Common Pitfall: Integer Caching¶
Python caches small integers (-5 to 256):
```python a = 256 b = 256 print(a is b) # True (cached)
a = 257 b = 257 print(a is b) # False (not cached, usually) ```
Never use is to compare integers — use ==.
is not Operator¶
```python a = [1, 2] b = [1, 2]
print(a is not b) # True (different objects) print(a != b) # False (same value) ```
Practical Example¶
python
def process(data=None):
if data is None: # Correct: check for None singleton
data = []
# NOT: if data == None (works but not idiomatic)
return data
Custom Equality with __eq__¶
Classes can override == behavior:
```python class Point: def init(self, x): self.x = x
def __eq__(self, other):
return self.x == other.x
p1 = Point(3) p2 = Point(3)
print(p1 == p2) # True (custom equality) print(p1 is p2) # False (different objects) ```
==returnsTruedue to custom__eq__isstill returnsFalse— different instances
Hashability Contract¶
If you override __eq__, you must also override __hash__:
```python 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
def __hash__(self):
return hash((self.x, self.y))
```
The contract: If a == b, then hash(a) == hash(b).
Without __hash__, objects become unhashable:
```python class NoHash: def eq(self, other): return True
hash(NoHash()) # TypeError: unhashable type: 'NoHash' ```
Summary¶
| Operator | Compares | Use For |
|---|---|---|
== |
Value equality | Most comparisons |
is |
Object identity | None, True, False |
- Use
isonly for singleton comparisons - Use
==for value comparisons - Mutable objects:
isalmost alwaysFalsefor separate creations - Don't rely on integer/string interning behavior
Exercises¶
Exercise 1.
Create two empty lists using separate expressions. Verify that they are equal (==) but not identical (is). Then assign one to the other and verify they become identical.
Solution to Exercise 1
```python a = [] b = [] print(a == b) # True (same contents) print(a is b) # False (different objects)
c = a print(a is c) # True (same object) ```
Each [] literal creates a new list object. After c = a, both names refer to the same object.
Exercise 2.
Demonstrate Python's integer caching by showing that a is b is True for a = b = 256 but may be False for a = b = 257. Explain why you should never rely on this behavior.
Solution to Exercise 2
```python a = 256 b = 256 print(a is b) # True (cached)
a = 257 b = 257 print(a is b) # May be False (not cached) ```
CPython caches integers from -5 to 256. Beyond that range, each creation may produce a new object. Never use is to compare integer values -- always use ==.
Exercise 3.
Write a function process(data=None) that creates a new empty list when no argument is passed. Explain why the check uses is None rather than == None.
Solution to Exercise 3
```python def process(data=None): if data is None: data = [] data.append("processed") return data
print(process()) # ['processed'] print(process([1, 2, 3])) # [1, 2, 3, 'processed'] ```
is None checks identity against the None singleton. Using == None would work but is not idiomatic, and a custom class could override __eq__ to return True when compared to None, leading to unexpected behavior.