Оператор 'is' ведет себя неожиданно с плавающей точкой

Я столкнулся с запутанной проблемой при модульном тестировании модуля. Модуль на самом деле приводит значения, и я хочу сравнить эти значения.

Есть разница по сравнению с== а такжеis (частично, я остерегаюсь разницы)

>>> 0.0 is 0.0
True   # as expected
>>> float(0.0) is 0.0
True   # as expected

Как и ожидалось до сих пор, но вот моя «проблема»:

>>> float(0) is 0.0
False
>>> float(0) is float(0)
False

Зачем? По крайней мере, последний действительно смущает меня. Внутреннее представлениеfloat(0) а такжеfloat(0.0) должно быть равным. В сравнении с== работает как положено.

 Günther Jena08 авг. 2016 г., 19:30
@MartijnPieters, @Elazar: я должен признать, что я не знал, чтоis сравнивает идентификаторы. Так даfloat(1.0) is 1.0 на самом деле удивительно ;-)
 Elazar08 авг. 2016 г., 19:21
 Elazar08 авг. 2016 г., 21:01
Могу ли я подать автоматический повторный голос? Этот вопросабсолютно не дубликат соответствующего вопроса, и там точно не дано никакого ответа.
 Elazar08 авг. 2016 г., 19:24
Ваш вопрос заслуживает ответа, но если вы столкнулись с этой проблемой в реальном коде, код, вероятно, ошибочный и должен быть исправлен. Нет (почти) никаких причин для проверки ссылочной идентичности между поплавками таким способом.
 Martijn Pieters08 авг. 2016 г., 19:26
Вы уверены, что понимаете?is? Почему ты ожидал0.0 is 0.0 быть правдой, так как это на самом делеудивительный для некоторых.
 Günther Jena08 авг. 2016 г., 19:39
@Elazar: спасибо за добавление тега cpython, так как он кажется очень связанным с cpython и от чего вы не должны зависеть (как вы уже упоминали)
 tobias_k08 авг. 2016 г., 19:25
Странно то, что, хотя я могу воспроизвести это, всеid(0.0), id(float(0.0)) а такжеid(float(0)) вернуть то же значение. ... То есть значение остается тем же, если я выполняю их один за другим в интерактивной оболочке, но если я выполняюid(float(0.0)), id(float(0)) (как кортеж), то идентификаторы отличаются. Любое объяснение?
 Martijn Pieters08 авг. 2016 г., 21:26
@tobias_k: две причины: неизменные литералы в коде хранятся как константы с объектом кода (так0.0 is 0.0 производит только один объект, который используется повторно, Python повторно использует память (такid(0.0) сопровождаемый другимid(someobject) может легко создать тот же идентификатор, так как предыдущий был собран мусором) и создание кортежа не может повторно использовать места в памяти, так как вам все еще нужновсе объекты, чтобы быть частью этого кортежа.

Ответы на вопрос(2)

Решение Вопроса

Это связано с тем, какis работает. Он проверяет ссылки вместо значения. ВозвращаетсяTrue если какой-либо аргумент назначен тому же объекту.

В этом случае это разные экземпляры;float(0) а такжеfloat(0) имеют одинаковое значение==, но это разные сущности для Python. Реализация CPython также кэширует целые числа как одноэлементные объекты в этом диапазоне ->[х | x ∈ ℤ ∧ -5 ≤ x ≤ 256]:

>>> 0.0 is 0.0
True
>>> float(0) is float(0)  # Not the same reference, unique instances.
False

В этом примере мы можем продемонстрировать целое числопринцип кэширования:

>>> a = 256
>>> b = 256
>>> a is b
True
>>> a = 257
>>> b = 257
>>> a is b
False

Теперь, если поплавки переданыfloat(), литерал с плавающей точкой просто возвращается (короткое замыкание), как в той же ссылке, так как нет необходимости создавать экземпляр нового float из существующего float:

>>> 0.0 is 0.0
True
>>> float(0.0) is float(0.0)
True

Это может быть продемонстрировано в дальнейшем с помощьюint() также:

>>> int(256.0) is int(256.0)  # Same reference, cached.
True
>>> int(257.0) is int(257.0)  # Different references are returned, not cached.
False
>>> 257 is 257  # Same reference.
True
>>> 257.0 is 257.0  # Same reference. As @Martijn Pieters pointed out.
True

Тем не менее, результатыis также зависят от области, в которой выполняетсяза пределами этого вопроса / объяснения), пожалуйста, обратитесь к пользователю:@Джимфантастическое объяснение накодовые объекты, Даже документ Python включает в себя раздел об этом поведении:

5.9 Сравнения

[7] Из-за автоматической сборки мусора, свободных списков и динамической природы дескрипторов вы можете заметить необычное поведение в некоторых случаяхis оператор, как те, которые включают сравнения между методами экземпляра, или константы. Проверьте их документацию для получения дополнительной информации.

 Günther Jena08 авг. 2016 г., 19:22
@Jim ответил на это
 ospahiu08 авг. 2016 г., 19:28
@ user2357112 Да, вы правы, соответственно обновил мой ответ.
 Martijn Pieters08 авг. 2016 г., 19:24
Реализация CPython также кэширует целые числа / числа с плавающей точкой как одноэлементные объекты в этом диапазоне -> [x> = -5 | х <= 256], Не толькоint объекты интернированы. Здесь происходит то, чтолитералы хранятся как константы с объектом кода, и нет смысла хранить два0.0 значения в том же массиве констант объекта кода.
 user235711208 авг. 2016 г., 19:24
CPython не кэширует поплавки. Вы видите сочетание постоянного складывания иfloat проходящий конструктор плавает напрямую для случаев, когдаis даетTrue.
 Günther Jena08 авг. 2016 г., 19:21
float(17.0) is float(17.0) возвращаетсяTrue ноfloat(17.0) is float('17.0') возвращаетсяFalse -> этоfloat( some_value ) просто вернуть исходное значение с плавающей точкой, чтобы новый экземпляр не создавался (в случае, если some_value является значением с плавающей точкой ...)?
 Martijn Pieters08 авг. 2016 г., 19:27
Проверятьcompile('0.0 is 0.0', '', 'exec').co_consts; ты найдешьодин 0.0 объект иNone.

float объект поставляетсяfloat(), CPython* просто возвращает его без создания нового объекта.

Это можно увидеть вPyNumber_Float (который в конечном итоге вызывается изfloat_new) где объектo передано в проверено сPyFloat_CheckExact; еслиTrue, он просто увеличивает количество ссылок и возвращает его:

if (PyFloat_CheckExact(o)) {
    Py_INCREF(o);
    return o;
}

В результатеid объекта остается прежним. Итак, выражение

>>> float(0.0) is float(0.0) 

сводится к:

>>> 0.0 is 0.0

Но почему это равноTrue? Что ж,CPython имеет некоторыемаленький оптимизаций.

В этом случае он использует один и тот же объект для двух вхождений0.0 в вашей команде, потому что они являются частьютот же самыйcode объект (короткий отказ от ответственности: они находятся на одной логической линии); Итакis Тест пройдёт успешно.

Это может быть дополнительно подтверждено, если вы выполнитеfloat(0.0) в отдельных строках (или, разделенных;) а такжезатем проверить личность:

a = float(0.0); b = float(0.0) # Python compiles these separately
a is b # False 

С другой стороны, еслиint (илиstr), CPython создастновый float возразить от него и вернуть это. Для этого он используетPyFloat_FromDouble а такжеPyFloat_FromString соответственно.

В результате возвращаемые объекты отличаютсяids (который используется для проверки личности сis):

# Python uses the same object representing 0 to the calls to float
# but float returns new float objects when supplied with ints
# Thereby, the result will be False
float(0) is float(0) 

*Заметка: Все вышеупомянутое поведение применяется для реализации Python вC т.е.CPython, Другие реализации могут демонстрировать другое поведение. Короче,не зависит от этого.

 ospahiu08 авг. 2016 г., 19:54
Я добавил вашу часть об объектах кода в качестве сноски (кстати, отличное объяснение!).
 Günther Jena08 авг. 2016 г., 19:33
Спасибо за ваш ответ с вашими данными, хотя я приму ответ mrdomoboto как правильный (я ненавижу это трудное решение ...)

Ваш ответ на вопрос