Оператор "is" ведет себя неожиданно с целыми числами
Почему следующий код ведет себя неожиданно в Python?
>>> a = 256
>>> b = 256
>>> a is b
True # Это ожидаемый результат
>>> a = 257
>>> b = 257
>>> a is b
False # Что здесь произошло? Почему это False?
>>> 257 is 257
True # Тем не менее, литеральные числа сравниваются корректно
Я использую Python 2.5.2. Проверив другие версии Python, я заметил, что в Python 2.3.3 наблюдается аналогичное поведение для чисел от 99 до 100.
Исходя из вышеизложенного, я могу предположить, что Python реализует "маленькие" целые числа и "большие" целые числа по-разному, и оператор is
может это различать. Почему такая "протечка абстракции"? Какой более подходящий способ сравнения двух произвольных объектов, чтобы узнать, являются ли они одинаковыми, если я заранее не знаю, числа они или нет?
4 ответ(ов)
Это зависит от того, хотите ли вы проверить, равны ли два объекта, или являются ли они одним и тем же объектом.
is
проверяет, указывают ли оба объекта на один и тот же объект, а не просто равны ли они. Вероятно, маленькие целые числа указывают на одно и то же место в памяти для экономии пространства.
In [29]: a = 3
In [30]: b = 3
In [31]: id(a)
Out[31]: 500729144
In [32]: id(b)
Out[32]: 500729144
Для сравнения равенства произвольных объектов следует использовать ==
. Поведение этого сравнения можно задать с помощью атрибутов __eq__
и __ne__
.
Вы правы, ваша гипотеза кажется верной. В Python числа в диапазоне от -5 до 256 (включительно) кэшируются для оптимизации, что и объясняет одинаковые значения идентификаторов для объектов с числами 255.
Посмотрите на следующий пример:
In [1]: id(255)
Out[1]: 146349024
In [2]: id(255)
Out[2]: 146349024 # Тот же идентификатор, так как 255 кэшируется.
In [3]: id(257)
Out[3]: 146802752
In [4]: id(257)
Out[4]: 148993740 # Разные идентификаторы, так как 257 не кэшируется.
In [5]: a = 255
In [6]: b = 255
In [7]: c = 257
In [8]: d = 257
In [9]: id(a), id(b), id(c), id(d)
Out[9]: (146349024, 146349024, 146783024, 146804020)
Как видно из результатов, a
и b
, оба равные 255, имеют один и тот же идентификатор, в то время как c
и d
, которые равны 257, имеют разные идентификаторы. Это подтверждает, что числа <= 255
обрабатываются как литералы (или кэшируются), в то время как числа больше этого значения создаются как новые объекты.
В случае с неизменяемыми значимыми объектами, такими как целые числа, строки или даты, идентичность объектов не имеет особого значения. Лучше сосредоточиться на равенстве. Идентичность является, по сути, деталью реализации для значимых объектов — поскольку они неизменяемы, нет практической разницы между несколькими ссылками на один и тот же объект или несколькими разными объектами.
Такое поведение связано с особенностями управления памятью в Python, особенно для строк. Изначально Python использует оптимизацию для малых и общепринятых строк: когда вы создаете строку, которая совпадает с уже существующей (например, строки короткие и часто используемые), интерпретатор может использовать одну и ту же область памяти, и соответственно, is
будет возвращать True
, так как обе переменные ссылаются на один и тот же объект.
Теперь давайте разберемся с вашим примером:
В первом случае, когда вы присваиваете
s = b = 'somestr'
,s
иb
указывают на одну и ту же строку в памяти. Поэтому какs == b
, так иs is b
вернутTrue
.Во втором случае с
s
иb
, оба опять указывают на одну и ту же строку, следовательно, результат такой же:True
.Однако в третьем примере, когда строки длиннее или менее характерные, Python может создать новые объекты в памяти для каждой строки при их инициализации. Когда вы присваиваете
s1
иb1
разные значения, даже если они содержат одинаковый текст, интерпретатор может выделить разные области памяти для них. Это приводит к тому, чтоs1 is b1
возвращаетFalse
, несмотря на то чтоs1 == b1
возвращаетTrue
, потому что содержимое строк совпадает, а ссылки на объекты различны.
Итак, если вы хотите проверить, указывают ли две переменные на один и тот же объект, используйте is
. Если вам нужно проверить равенство содержимого, используйте ==
.
Почему сравнение строк с помощью '==' и 'is' иногда дает разные результаты?
Поведение операторов инкремента и декремента в Python
`/` против `//` для деления в Python
Как клонировать список, чтобы он не изменялся неожиданно после присваивания?
Ошибка: "'dict' объект не имеет метода 'iteritems'"