Здесь есть две вещи, которые нужно знать о CPython и его поведении.Во-первых, маленькие целые числа в диапазоне [- 5, 256] внутри.Таким образом, любое значение, попадающее в этот диапазон, будет иметь один и тот же идентификатор, даже при REPL:
>>> a = 100
>>> b = 100
>>> a is b
True
Поскольку 300> 256, оно не интернируется:
>>> a = 300
>>> b = 300
>>> a is b
False
Во-вторых, это то, чтов скрипте литералы помещаются в постоянный раздел скомпилированного кода.Python достаточно умен, чтобы понять, что и a
, и b
относятся к литералу 300
и что 300
является неизменным объектом, он может просто идти вперед и ссылаться на одно и то же постоянное местоположение.Если вы немного доработаете свой сценарий и напишите его так:
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__)
Начальная часть вывода выглядит следующим образом:
2 0 LOAD_CONST 1 (300)
2 STORE_FAST 0 (a)
3 4 LOAD_CONST 1 (300)
6 STORE_FAST 1 (b)
...
Как видите, CPython загружает a
и b
с использованием одного и того же постоянного слота.Это означает, что a
и b
теперь ссылаются на один и тот же объект (потому что они ссылаются на один и тот же слот), и именно поэтому a is b
в сценарии True
, но не в REPL.
Такое поведение можно увидеть и в REPL, если вы оберните свои операторы в функцию:
>>> 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...
Итог: хотя CPython время от времени проводит эти оптимизации, вы не должны действительно рассчитывать на это:это действительно детали реализации, и они со временем менялись (например, CPython делал это только для целых чисел до 100).Если вы сравниваете числа, используйте ==
.: -)