Swiftorial Logo
Home
Swift Lessons
Tutorials
Learn More
Career
Resources

Python FAQ: Top Questions

17. What is the difference between `is` and `==` in Python?

In Python, both `is` and `==` are used for comparison, but they serve fundamentally different purposes: `is` checks for object identity, while `==` checks for value equality.

1. `is` operator:

  • Purpose: The `is` operator tests for **object identity**. It checks if two variables point to the *exact same object in memory*.
  • Underlying Mechanism: It essentially compares the memory addresses (IDs) of the two objects. You can get an object's memory address using the built-in `id()` function. `a is b` is equivalent to `id(a) == id(b)`.
  • Use Cases:
    • Checking if a variable is `None` (e.g., `if my_var is None:` is standard and preferred over `my_var == None:`).
    • Checking if two variables refer to the same instance of an object.
    • For performance-critical code where you strictly need to know if two variables are aliases for the exact same object.
  • Important Note: For immutable objects like small integers, strings, or `None`, Python often performs "interning" (or caching) to optimize memory usage. This means that multiple variables with the same value might actually refer to the same object in memory, leading `is` to return `True` unexpectedly. This is an implementation detail and should not be relied upon for general equality checks. For larger strings or integers outside a specific range, interning is less likely, and `is` might return `False` even for identical values.

2. `==` operator:

  • Purpose: The `==` operator tests for **value equality**. It checks if the values of two objects are the same.
  • Underlying Mechanism: It uses the object's `__eq__()` method. If the `__eq__()` method is not implemented for a custom class, Python falls back to comparing object IDs (like `is`), but for built-in types, it compares their contents or values.
  • Use Cases:
    • Comparing the contents of two lists, tuples, dictionaries, or sets.
    • Comparing the values of numbers or strings.
    • Generally, for checking if two objects have the same "value" or "state."

Summary Table:

Operator Checks For Compares Behavior with Mutable Objects Behavior with Immutable Objects
`is` Object Identity Memory Address (ID) `True` only if they are the exact same object (e.g., `list_a = list_b`). `True` only if they are the exact same object. Can be `True` for identical small values due to interning (e.g., `1 is 1`, `None is None`).
`==` Value Equality Content / Value (via `__eq__`) `True` if their contents are the same, even if they are different objects. `True` if their values are the same.

In most general programming scenarios where you want to know if two variables hold the same data, you should use `==`. Reserve `is` for when you specifically need to confirm that two variables refer to the *exact same object* in memory.


# --- Example 1: Comparing Integers (Immutable, often Interned) ---
print("--- Integers ---")
a = 10
b = 10
c = 1000 # Larger integer
d = 1000

print(f"a = {a}, b = {b}, c = {c}, d = {d}")
print(f"id(a): {id(a)}, id(b): {id(b)}, id(c): {id(c)}, id(d): {id(d)}")

# 'is' for small integers (often interned by CPython)
print(f"a is b: {a is b}")     # Typically True (due to interning)
print(f"a == b: {a == b}")     # True (values are same)

# 'is' for larger integers (less likely to be interned consistently)
print(f"c is d: {c is d}")     # Can be False (even if values are same, often not interned)
print(f"c == d: {c == d}")     # True (values are same)


# --- Example 2: Comparing Strings (Immutable, small strings often interned) ---
print("\n--- Strings ---")
str1 = "hello"
str2 = "hello"
str3 = "world!" * 100 # A long string
str4 = "world!" * 100

print(f"id(str1): {id(str1)}, id(str2): {id(str2)}")
print(f"id(str3): {id(str3)}, id(str4): {id(str4)}")

print(f"str1 is str2: {str1 is str2}") # Typically True (small strings interned)
print(f"str1 == str2: {str1 == str2}") # True (values are same)

print(f"str3 is str4: {str3 is str4}") # Can be False (long strings less likely interned)
print(f"str3 == str4: {str3 == str4}") # True (values are same)


# --- Example 3: Comparing Lists (Mutable Objects) ---
print("\n--- Lists (Mutable) ---")
list1 = [1, 2, 3]
list2 = [1, 2, 3]
list3 = list1 # list3 is a reference to list1

print(f"id(list1): {id(list1)}, id(list2): {id(list2)}, id(list3): {id(list3)}")

print(f"list1 is list2: {list1 is list2}") # False (different objects, same value)
print(f"list1 == list2: {list1 == list2}") # True (values are same)

print(f"list1 is list3: {list1 is list3}") # True (list3 refers to the exact same object as list1)
print(f"list1 == list3: {list1 == list3}") # True (values are same)

# Modify list1 and see its effect on list3
list1.append(4)
print(f"list1 after append: {list1}")
print(f"list3 after list1 append: {list3}") # list3 also changed because it's the same object


# --- Example 4: Comparing `None` (Singleton Object) ---
print("\n--- None ---")
val1 = None
val2 = None
val3 = 10 # Not None

print(f"val1 is val2: {val1 is val2}") # True (None is a singleton, always the same object)
print(f"val1 == val2: {val1 == val2}") # True (values are same)

print(f"val1 is val3: {val1 is val3}") # False
print(f"val1 == val3: {val1 == val3}") # False

# Preferred way to check for None
if val1 is None:
    print("val1 is indeed None (preferred check)")
if val1 == None:
    print("val1 equals None (works but less Pythonic)")

        

Explanation of the Example Code:

  • **Integers:**
    • For `a` and `b` (small integers), `a is b` is `True` because CPython typically interns (caches) small integer objects for efficiency. Both variables point to the same object. `a == b` is also `True` because their values are identical.
    • For `c` and `d` (larger integers), `c is d` is often `False`. Python does not guarantee interning for larger integers, so `c` and `d` might be separate objects even if their values are the same. However, `c == d` is still `True` as it compares values.
  • **Strings:**
    • Similar to integers, small identical strings (`str1`, `str2`) are frequently interned, making `str1 is str2` `True`.
    • Longer strings (`str3`, `str4`) are less likely to be interned by default, so `str3 is str4` might be `False`, even though their values (`str3 == str4`) are equal.
  • **Lists (Mutable Objects):**
    • `list1` and `list2` have the same content/value, so `list1 == list2` is `True`. However, they are distinct objects created independently, so `list1 is list2` is `False`.
    • `list3 = list1` creates a direct reference. Both `list1` and `list3` now point to the *exact same list object* in memory. Therefore, `list1 is list3` is `True`, and naturally, `list1 == list3` is also `True`. Any modification to `list1` (like `append(4)`) will immediately be visible in `list3` because they are the same object.
  • **`None` (Singleton):**
    • `None` is a unique singleton object in Python. There is only ever one instance of `None` in memory. Thus, `val1 is val2` (where both are `None`) will *always* be `True`.
    • Checking `if my_variable is None` is the canonical and most Pythonic way to test for `None` because it's guaranteed to be correct and efficient.

This thorough examination of `is` and `==` with various data types demonstrates that `is` is about identity (same memory location), while `==` is about equality (same value), with Python's internal optimizations (interning) sometimes making `is` behave like `==` for small immutable objects.