In Python 3.9, 256 is 256 is True, but 257 is 257 is False. And in Python 3.15, the answer changed. Here's why, and the real bug it causes.
Video version:
The weird part
Python 3.9, typed line by line in the REPL:
>>> a = 256
>>> b = 256
>>> a is b
True
>>> x = 257
>>> y = 257
>>> x is y
False # 🤯
>>> x == y
True
Same numbers, different answers.
== compares values, is compares identity
-
a == basks: do they have the same value? -
a is basks: are they the same object in memory? (likeid(a) == id(b))
Two lists with the same items are equal, but they're two different objects:
>>> [1, 2] == [1, 2]
True
>>> [1, 2] is [1, 2]
False
Why 256 "works": the small integer cache
To save memory, CPython creates small integers once and reuses them, so every 256 is the same object. Outside that range, each new int is usually a new object.
I measured the cached range by parsing numbers with int("…") until is stopped returning True:
| Python | cached range |
|---|---|
| 3.9.6 | -5 to 256 |
| 3.14.10 | -5 to 256 |
| 3.15.2 | -5 to 1024 |
So in Python 3.15:
>>> x = 257
>>> y = 257
>>> x is y
True # changed in 3.15!
>>> x = 1025
>>> y = 1025
>>> x is y
False
Code that uses is on numbers can give a different result just by upgrading Python. This is a CPython implementation detail, not a language rule, which is exactly why you should never rely on it.
Gotcha: writing
x = 257; y = 257on one line (or in the same file) can giveTrueeven on old versions, because the compiler reuses identical constants in the same code block. That makes this bug even harder to spot.
The real bug: works in tests, fails in production
def check(rows, expected):
total = len(rows)
if total is expected: # ❌ identity, not value
return "OK"
return "MISMATCH"
check(["u"] * 1000, 1000) # 'OK' on 3.15 (cached)
check(["u"] * 1500, 1500) # 'MISMATCH' 😱
Your tests with 1,000 rows pass. Production with 1,500 rows fails, even though the numbers are equal. The fix is simply ==.
Python even warns you
If you use is with a literal, Python emits a warning:
SyntaxWarning: "is" with 'int' literal. Did you mean "=="?
if x is 257:
Don't ignore it.
When to use is
✅ Use is for:
-
x is None/x is not None - sentinels and singletons (
x is MISSING) - when you really mean the same object
❌ Use == for numbers, strings, lists, dicts: any value comparison.
Why is None instead of == None? A class can override == to return anything:
class Weird:
def __eq__(self, other):
return True
w = Weird()
w == None # True (lies!)
w is None # False (can't be faked)
Go search your code for is followed by a number or string literal. 👀
I post one tested coding tip every day on YouTube at Refactorly. The 30-second version of this one is here.
Top comments (0)