Object identity: id() and memory references
Overview
Every Python object has a unique identity — its location in memory — which you read with `id()`. The `is` operator compares identity (are these the exact same object?), while `==` compares value (do they have equal contents?). Two separate lists with the same items are `==` but not `is`, because they are distinct objects in memory. Only a name assigned from another (like `c = a`) shares identity. Think of `id()` as a house's street address and `==` as two houses having identical furniture: the same address is literally the same house, but identical furniture is not. # Identity sharing trap a = [1, 2] b = a b.append(3) print(a) # [1, 2, 3] - 'a' was modified! When multiple variables point to the exact same mutable object in memory, modifying one variable modifies the underlying object for all variables sharing that identity. This is covered fully in `shallow-vs-deep-copy`.
Confusing `is` with `==` causes real bugs in production code. You should reserve `is` exclusively for checking singletons like `None`, `True`, and `False`. Checking values with `is` (like `x is 1000`) can pass or fail unpredictably because Python only caches some objects in memory. Always use `==` when comparing values to ensure correct behavior.
Where used: None checks in API handlers, Pydantic validators, debugging shared references
Why learn this
- Writing correct `None` checks (like `if x is None:`) in FastAPI handlers and Pydantic validators.
- Debugging why two 'equal' objects behave differently because they are actually the same object in memory.
Code walkthrough
a = {'id': 1}
b = {'id': 1}
c = a
print(a == b)
print(a is b)
print(a is c)
Focus: The dictionaries `a` and `b` are equal (`==`) but not identical (`is`). Only `c`, assigned from `a`, is the same object in memory.
Aha moment
a = 256
b = 256
print(a is b)
big = 1000
print(big is int('1000'))
Prediction: Both lines compare equal integers with `is`. What does each print?
Common guess: True for both
CPython caches small integers (from `-5` to `256`), so `256` is always that one cached object (so `is` evaluates to `True`). The integer `1000` is outside the cache, and `int('1000')` builds a fresh object at runtime, so it is a different object (so `is` evaluates to `False`). Use `==` for values, and reserve `is` for `None`, `True`, and `False`.
Common mistakes
- Using `is` to compare values: Checking `x is 1000` can be `False` even when `x == 1000`, because CPython only caches small integers. Fix: use `==` for values, and reserve `is` for `None`, `True`, and `False`.
- Assuming `==` means the same object: Two dictionaries can evaluate to `True` with `==` yet be fully independent, so mutating one will not affect the other. Use `is` when you need to know they are literally the same object.
Glossary
- cached
- Storing frequently used data in a temporary, fast-access memory area so it doesn't have to be recreated every time. For example, CPython caches small integers so `a = 5; b = 5; a is b` evaluates to `True`.
- CPython
- The standard, original, and most widely used version of the Python programming language, written in C. For example, running `python script.py`.
Recall questions
- What does the `is` operator compare, versus `==`?
- Two lists have identical contents. Are they `==`? Are they `is`?
- When should you use `is` rather than `==`?
- If multiple variables share the exact same mutable object in memory, what happens when you modify the object through one of the variables?
Understanding checks
What will be printed when this script is run?
True, False, True
The check `a == b` evaluates to `True` because their contents match. The check `a is b` evaluates to `False` because `b` is a freshly created list with a different memory address. The check `a is c` evaluates to `True` because `c` is assigned directly from `a`, so they reference the exact same object in memory.
A developer is writing a validator that ensures a user's balance matches exactly `1000`. Identify the bug.
The function uses the `is` operator to check for equality of the value `1000`.
Because CPython only caches small integers, using `is` to compare values like `1000` can fail unpredictably depending on how the object was created in memory. To compare values, you must use `==`. The `is` operator should only be used for `None`, `True`, and `False`.
Practice tasks
Fixing the Identity Check
The function below incorrectly uses `is` to check if a user's input matches a specific integer, and uses `==` to check if a result is `None`. Modify the code to use the correct operators for value comparison and identity checking.
Challenge
A correct None check
Write `get_or_default(value, default)` that returns `default` ONLY when `value` is `None` — not when `value` is `0`, `''`, or an empty list. Use the correct operator, then print three calls to prove `0` and `''` are kept.
Continue learning
Previous: Primitive types: int, float, bool, str
Previous: Mutable vs immutable objects
Next: Shallow vs Deep Copy