Variables as 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 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; only a name assigned from another (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, identical furniture is not.
Confusing `is` with `==` causes real bugs. The safe rule: use `is` only for None, True, and False, and use `==` for comparing values. Checking values with `is` (e.g. x is 1000) can pass or fail unpredictably because only some objects are cached.
Where used: None checks in API handlers, Pydantic validators, debugging shared references
Why learn this
- Writing correct None checks (if x is None) in FastAPI handlers and Pydantic validators
- Debugging why two 'equal' objects behave differently because they are actually the same object
Code walkthrough
a = {'id': 1}
b = {'id': 1}
c = a
print(a == b)
print(a is b)
print(a is c)
Focus: a and b are equal (==) but not identical (is); only c, assigned from a, is the same object.
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 (-5 to 256), so 256 is always that one cached object (is is True). 1000 is outside the cache, and int('1000') builds a fresh object at runtime, so it is a different object (is is False). Use == for values; reserve is for None/True/False.
Common mistakes
- Using `is` to compare values: x is 1000 can be False even when x == 1000, because CPython only caches small integers. Fix: use == for values; reserve is for None, True, and False.
- Assuming == means the same object: Two dicts can be == yet 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. e.g. CPython caches small integers so `a = 5; b = 5; a is b` is `True`.
- CPython
- The standard, original, and most widely used version of the Python programming language, written in C. Example: `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 `==`?
Understanding checks
What will be printed when this script is run?
True, False, True
`a == b` is True because their contents match. `a is b` is False because `b` is a freshly created list with a different memory address. `a is c` is 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 users balance matches exactly 1000. Identify the bug.
The function uses `is` 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 `==`. `is` 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 users 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: Mutable vs immutable types
Next: Shallow vs deep copy