There are actually two things to know about CPython and its behavior here.
First, small integers in the range of [-5, 256] are interned internally.
So any value falling in that range will share the same id, even at the REPL:
>>> a = 100
>>> b = 100
>>> a is b
True
Since 300 > 256, it's not being interned:
>>> a = 300
>>> b = 300
>>> a is b
False
Second, is that in a script, literals are put into a constant section of the
compiled code. Python is smart enough to realize that since both a
and b
refer to the literal 300
and that 300
is an immutable object, it can just
go ahead and reference the same constant location. If you tweak your script
a bit and write it as:
def foo():
a = 300
b = 300
print(a==b)
print(a is b)
print("id(a) = %d, id(b) = %d" % (id(a), id(b)))
import dis
dis.disassemble(foo.__code__)
The beginning part of the output looks like this:
2 0 LOAD_CONST 1 (300)
2 STORE_FAST 0 (a)
3 4 LOAD_CONST 1 (300)
6 STORE_FAST 1 (b)
...
As you can see, CPython is loading the a
and b
using the same constant slot.
This means that a
and b
are now referring to the same object (because they
reference the same slot) and that is why a is b
is True
in the script but
not at the REPL.
You can see this behavior in the REPL too, if you wrap your statements in a function:
>>> import dis
>>> def foo():
... a = 300
... b = 300
... print(a==b)
... print(a is b)
... print("id(a) = %d, id(b) = %d" % (id(a), id(b)))
...
>>> foo()
True
True
id(a) = 4369383056, id(b) = 4369383056
>>> dis.disassemble(foo.__code__)
2 0 LOAD_CONST 1 (300)
2 STORE_FAST 0 (a)
3 4 LOAD_CONST 1 (300)
6 STORE_FAST 1 (b)
# snipped...
Bottom line: while CPython makes these optimizations at times, you shouldn't really count on it--it's really an implementation detail, and one that they've changed over time (CPython used to only do this for integers up to 100, for example). If you're comparing numbers, use ==
. :-)