Python is identity vs equality

PYTHON Published Updated 3 mins read By Leon Wei
Python is identity vs equality cover image

Quick summary

Summarize this blog with AI

Use == to ask whether two values compare equal. Use is to ask whether two names refer to the exact same object. Equal values do not generally imply shared identity. Identity checks are useful for None, explicit sentinels, and intentional aliasing; they are not a shortcut for comparing numbers or strings.

These examples use Python 3.12.14 and the standard library. Run the blocks in order. None relies on a particular integer cache or compiler constant-folding decision.

Equal lists can be different objects

first = [10, 20]
second = [10, 20]
alias = first
print(first == second)
print(first is second)
print(first is alias)
alias.append(30)
print(first)
print(second)

Expected output:

True
False
True
[10, 20, 30]
[10, 20]

The two list expressions construct distinct lists. Assignment to alias does not copy the list: it binds another name to the same object. Appending through that name changes the shared object. Equality compares list contents; identity reveals whether a mutation would reach the same list.

A shallow copy separates only the outer container

import copy

original = [[1], [2]]
shallow = original.copy()
deep = copy.deepcopy(original)
print(original is shallow)
print(original[0] is shallow[0])
shallow[0].append(9)
print(original)
print(deep)

Expected output:

False
True
[[1, 9], [2]]
[[1], [2]]

The shallow copy has a new outer list but retains references to the nested lists. A deep copy recursively copies this fixture's nested values. That is a copying decision, not a reason to replace value comparisons with identity tests. A tuple can also hold a mutable object, so an immutable outer container does not make its entire object graph immutable.

Check None and a private sentinel by identity

MISSING = object()
settings = {'limit': None, 'enabled': False, 'count': 0}

for key in ('limit', 'absent', 'enabled', 'count'):
    value = settings.get(key, MISSING)
    if value is MISSING:
        state = 'missing'
    elif value is None:
        state = 'explicit None'
    else:
        state = repr(value)
    print(key, state)

Expected output:

limit explicit None
absent missing
enabled False
count 0

One sentinel instance distinguishes an absent key from a key explicitly set to None. Recreating object() inside the comparison would create a different sentinel. A truth test such as if not value would also match zero, False, and empty containers, so it cannot express the same four-way distinction.

Equality belongs to the object's type

from dataclasses import dataclass

@dataclass(frozen=True)
class Product:
    sku: str

left = Product('A-10')
right = Product('A-10')
print(left == right)
print(left is right)
print(left == 'A-10')

Expected output:

True
False
False

The dataclass supplies field-based equality, so separate Product instances can compare equal. For a hand-written __eq__, return NotImplemented for unsupported comparison types so Python can follow its comparison protocol. Equality can be customized; identity cannot be overloaded this way. Some libraries return an array of comparisons from == rather than a single boolean, so use their documented aggregate comparison when checking entire arrays or tables.

Do not depend on literal interning

An interpreter may reuse some immutable objects. Results from a is b for equal integer or string values can differ between interactive input, a compiled script, optimization levels, and implementations. The size of an integer cache is not a language-level value-comparison contract. Likewise, separately incrementing numbers around 256 is not a portable way to demonstrate identity.

If you intentionally use string interning, call its explicit interface and keep the returned objects. The following result follows from that interface, rather than guessing which literal the compiler will reuse.

import sys

one = sys.intern(''.join(['product', '_code']))
two = sys.intern('product_code')
print(one == two)
print(one is two)

Expected output:

True
True

This does not make interning a default performance recommendation. Measure a real workload before adding it, and continue using == for ordinary text comparisons. Numeric values should also use equality even when an identity test happens to pass in one session.

Choose the question before the operator

For a value comparison, use == or the library's documented equivalent. For “is this the same object?” use is. For “was this optional value supplied?” use a stable sentinel or is None according to the input contract. Test aliasing with independently constructed mutable objects so the lesson remains valid across interpreters.

See dictionary keys and equality for the related hash contract. Primary references: Python's identity comparisons, copying operations, and explicit string interning.

Interview Prep

Begin Your SQL, Python, and R Journey

Master 230 interview-style coding questions and build the data skills needed for analyst, scientist, and engineering roles.

Related Articles

All Articles
Python return statement cover image
python Oct 17, 2026

Python return statement

Learn Python programming by the return statement in functions, how it works for code reuse and clarity, starting with basic function creation to…

Python while loop cover image
python Oct 17, 2026

Python while loop

Python while loops, a key concept in programming. Learn how looping enables repeated code execution, a crucial tool for efficient and effective …