is in Python: Identity Operator Explained

Master the Python IS operator for identity comparison. Learn the difference between is and ==, understand object identity, memory allocation, and avoid common pitfalls with practical examples.

The Python is operator is an identity operator that checks whether two variables point to the same object in memory. Unlike the == operator which compares values for equality, is compares object identity by checking if two references point to the exact same memory location. This distinction is crucial for writing correct and efficient Python code.

IS vs == Operator

The key difference between is and == is that == compares values (equality) while is compares identity (same object in memory).

Example 1: Basic Difference

# Value comparison with ==
a = [1, 2, 3]
b = [1, 2, 3]

print(a == b)  # Output: True (same values)
print(a is b)  # Output: False (different objects)

# Identity comparison
c = a
print(a is c)  # Output: True (same object)

# Memory locations
print(f"id(a): {id(a)}")
print(f"id(b): {id(b)}")
print(f"id(c): {id(c)}")
Output:
True
False
True
id(a): 140234567890
id(b): 140234567920
id(c): 140234567890

The == operator returns True because both lists have the same content, but is returns False because they are separate objects in memory.

Understanding Object Identity with id()

The id() function returns the unique identifier (memory address) of an object, which the is operator uses for comparison.

Example: Using id() Function

x = [1, 2, 3, 4, 5]
y = [1, 2, 3, 4, 5]
z = x

# Check identity with 'is'
print(x is z)    # Output: True
print(x is y)    # Output: False

# Verify with id()
print(f"id(x): {id(x)}")
print(f"id(y): {id(y)}")
print(f"id(z): {id(z)}")

# x and z have the same id
# y has a different id
Output:
True
False
id(x): 140234567890
id(y): 140234567920
id(z): 140234567890

The id() function returns the unique identifier (memory address) of an object, which the is operator uses for comparison.

IS NOT Operator

The is not operator returns True when two variables refer to different objects in memory.

Example: Using is not

a = [1, 2, 3, 4, 5]
b = [1, 2, 3, 4, 5]
c = a

# Using 'is not'
print(a is not c)  # Output: False (they are the same object)
print(a is not b)  # Output: True (different objects)

# Equivalent to: not (a is b)
print(not (a is b))  # Output: True
Output:
False
True
True

Operator Comparison Table:

Expression Meaning Use Case
a is b Same object in memory Check object identity
a is not b Different objects in memory Check object difference
a == b Equal values Check value equality
a != b Different values Check value inequality

When to Use IS Operator

The is operator should be used in specific scenarios where object identity matters more than value equality.

Use Case 1: Checking for None

def process_data(value):
    # CORRECT: Always use 'is' with None
    if value is None:
        print("No data provided")
        return
    
    print(f"Processing: {value}")

# WRONG: Don't use == with None
def wrong_check(value):
    if value == None:  # Not Pythonic
        print("This works but is not recommended")

process_data(None)  # Output: No data provided
process_data(42)    # Output: Processing: 42
Output:
No data provided
Processing: 42

Always use is or is not when comparing with None, as it's more explicit and faster.

Use Case 2: Checking for Boolean Singletons

def check_flag(flag):
    # Use 'is' for True/False when checking identity
    if flag is True:
        print("Flag is explicitly True")
    elif flag is False:
        print("Flag is explicitly False")
    else:
        print(f"Flag is truthy/falsy but not boolean: {flag}")

check_flag(True)   # Flag is explicitly True
check_flag(1)      # Flag is truthy/falsy but not boolean: 1
check_flag(False)  # Flag is explicitly False
check_flag(0)      # Flag is truthy/falsy but not boolean: 0
Output:
Flag is explicitly True
Flag is truthy/falsy but not boolean: 1
Flag is explicitly False
Flag is truthy/falsy but not boolean: 0

Use Case 3: Checking if Variables Reference Same List/Dict

def modify_list(original_list, new_list):
    if original_list is new_list:
        print("Warning: Same list reference, modifications will affect both")
        return False
    return True

my_list = [1, 2, 3]
same_ref = my_list
different_list = [1, 2, 3]

modify_list(my_list, same_ref)       # Warning message
modify_list(my_list, different_list) # Returns True
Output:
Warning: Same list reference, modifications will affect both

Python's Integer and String Interning

Python automatically interns small integers and some strings as an optimization, which can lead to surprising is behavior.

Example: Small Integer Caching

# Small integers (-5 to 256) are cached
a = 256
b = 256
print(a is b)  # Output: True

a = 257
b = 257
print(a is b)  # Output: False (usually, depends on implementation)

# This is due to Python's integer interning optimization
print(id(256) == id(256))  # True
print(id(257) == id(257))  # May vary
Output:
True
False
True
True

Example: String Interning

# String interning
a = "TutorialsPoint"
b = a
print(f"id(a), id(b): {id(a)}, {id(b)}")
print(f"a is b: {a is b}")          # Output: True
print(f"b is not a: {b is not a}")  # Output: False

# Identical string literals are often interned
x = "hello"
y = "hello"
print(x is y)  # Output: True (usually)

# But not always for dynamically created strings
x = "hello world"
y = "hello world"
print(x is y)  # Output: May be True or False

# Strings with spaces/special chars may not be interned
x = "hello world!"
y = "hello world!"
print(x is y)  # Output: False (typically)
Output:
id(a), id(b): 140234567890, 140234567890
a is b: True
b is not a: False
True
True
False

Python automatically interns small integers and some strings as an optimization, which can lead to surprising is behavior. Never rely on this behavior in production code.

Common Pitfalls and Mistakes

Understanding common mistakes when using the is operator helps avoid bugs and write more reliable code.

Mistake 1: Using IS for Value Comparison

# WRONG: Using 'is' to compare values
a = 1000
b = 1000
if a is b:  # Unreliable!
    print("Equal")
else:
    print("Not equal")  # Usually prints this

# CORRECT: Use == for value comparison
if a == b:  # Reliable
    print("Equal")  # Always prints this for equal values

Mistake 2: Relying on Integer Interning

# Don't rely on this behavior!
def bad_comparison(x, y):
    if x is y:  # Only works reliably for small integers
        return True
    return False

print(bad_comparison(5, 5))      # True (cached)
print(bad_comparison(500, 500))  # False (not cached)

# CORRECT approach
def good_comparison(x, y):
    if x == y:  # Always reliable
        return True
    return False

print(good_comparison(5, 5))      # True
print(good_comparison(500, 500))  # True

Mistake 3: Misunderstanding Mutable vs Immutable Types

# Immutable types (int, str, tuple)
a = 42
b = 42
print(a is b)  # May be True (depends on interning)

# Mutable types (list, dict, set)
x = [1, 2]
y = [1, 2]
print(x is y)  # Always False (different objects)

# Assignment creates reference, not copy
z = x
print(x is z)  # True (same object)

# Use copy to create new object
import copy
w = copy.copy(x)
print(x is w)  # False (different objects)
print(x == w)  # True (same values)

Best Practices

Following best practices when using the is operator ensures reliable and Pythonic code.

Practice 1: Always Use IS with None

# GOOD: Checking for None
if value is None:
    handle_none()

# BAD: Using == with None
if value == None:  # Works but not Pythonic
    handle_none()

Always use <code>is</code> and <code>is not</code> when comparing with <code>None</code>.

Practice 2: Use == for Value Comparison

# GOOD: Value comparison
if count == 0:
    print("Empty")

# BAD: Using 'is' for value comparison
if count is 0:  # Unreliable!
    print("Empty")

Use <code>==</code> for value comparisons in most other cases.

Practice 3: Use IS for Singleton Objects

# GOOD: Checking if same list
if original_list is modified_list:
    print("Same object")

# GOOD: Checking if equal values
if list1 == list2:
    print("Equal contents")

Use <code>is</code> for singleton objects (<code>None</code>, <code>True</code>, <code>False</code>) and to check if two variables reference the same mutable object.

Practice 4: Never Rely on Interning

# BAD: Relying on integer interning
if x is 256:  # May work, but unreliable
    do_something()

# GOOD: Use == for value comparison
if x == 256:  # Always reliable
    do_something()

Never rely on integer or string interning in production code.

Real-World Examples

These examples demonstrate practical uses of the is operator in real-world scenarios.

Example 1: Singleton Pattern Implementation

class DatabaseConnection:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            print("Creating new database connection")
        return cls._instance

# Test singleton
db1 = DatabaseConnection()
db2 = DatabaseConnection()

print(db1 is db2)  # Output: True (same instance)
print(id(db1) == id(db2))  # Output: True
Output:
Creating new database connection
True
True

Example 2: Cache Implementation

class Cache:
    def __init__(self):
        self._cache = {}
        self._MISSING = object()
    
    def get(self, key, default=None):
        result = self._cache.get(key, self._MISSING)
        
        if result is self._MISSING:
            print(f"Cache miss for key: {key}")
            return default
        
        print(f"Cache hit for key: {key}")
        return result
    
    def set(self, key, value):
        self._cache[key] = value

# Usage
cache = Cache()
cache.set('user_1', {'name': 'Alice'})

cache.get('user_1')        # Cache hit
cache.get('user_2')        # Cache miss
cache.get('user_2', {})    # Cache miss, returns {}
Output:
Cache hit for key: user_1
Cache miss for key: user_2
Cache miss for key: user_2

Example 3: Defensive Copy Detection

def modify_list_safely(original, new_items):
    # Check if caller passed the same list
    if original is new_items:
        raise ValueError("Cannot pass same list for both arguments")
    
    original.extend(new_items)
    return original

my_list = [1, 2, 3]
additional = [4, 5]

result = modify_list_safely(my_list, additional)
print(result)  # [1, 2, 3, 4, 5]

try:
    modify_list_safely(my_list, my_list)  # Raises ValueError
except ValueError as e:
    print(f"Error: {e}")
Output:
[1, 2, 3, 4, 5]
Error: Cannot pass same list for both arguments

Try it Yourself

Practice what you've learned by modifying the code below. Try changing the values and conditions to see different outputs!

Ready
main.py
Output Console 0 ms
// Click "Run Code" to see results

Related Topics

Frequently Asked Questions

What is the difference between 'not' and '!' in Python?

Python uses the keyword not for logical NOT operations. The symbol ! is used in other languages like C, Java, and JavaScript, but it will cause a SyntaxError in Python.

What is the difference between 'not' and 'not in'?

The not operator is a logical operator that negates boolean values. The not in operator is a membership operator that checks if an element is not in a sequence. They serve different purposes.

What is the precedence of 'not' compared to 'and' and 'or'?

The not operator has the highest precedence among logical operators, followed by and, then or. This means not a and b is evaluated as (not a) and b.

Should I use 'if not x:' or 'if x == False:'?

Prefer if not x: over if x == False:. The first checks for falsy values (None, 0, empty strings, etc.), while the second only checks for the boolean False value.

Can I use 'not' with non-boolean values?

Yes. Python evaluates values in boolean context. not converts the operand to boolean first, then negates it. Falsy values (0, None, empty collections) become True when negated.

How do I toggle a boolean value with 'not'?

You can use toggle = not toggle to flip a boolean value. This is a common pattern for alternating actions in loops or state management.