"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)
должно быть равным. Сравнение с ==
работает как ожидалось.
Ответы
Ответ 1
Это связано с тем, как работает is
. Он проверяет ссылки вместо значения. Он возвращает True
, если одному аргументу присваивается один и тот же объект.
В этом случае это разные экземпляры; float(0)
и float(0)
имеют одинаковое значение ==
, но представляют собой различные сущности, относящиеся к Python. Реализация CPython также кэширует целые числа как одиночные объекты в этом диапазоне → [x | 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 просто возвращается (закорочен), как в той же ссылке, что и нет необходимости создавать новый 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
также зависят от области, в которой он выполняется (за пределами этого вопроса/объяснения), пожалуйста, обратитесь к пользователю: @Jim фантастическое объяснение объектов кода. Даже документ python содержит раздел об этом поведении:
[7]Благодаря автоматической сборке мусора, свободным спискам и динамическому характеру дескрипторов вы можете заметить, по-видимому, необычное поведение при определенных применениях оператора is
, например, связанных с сравнениями между методами экземпляра или константами. Проверьте их документацию для получения дополнительной информации.
Ответ 2
Если объект 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
(или a str
), CPython создаст из него новый объект float
и вернет его. Для этого он использует PyFloat_FromDouble
и PyFloat_FromString
соответственно.
Эффект заключается в том, что возвращаемые объекты отличаются id
(который использовался для проверки тождеств с помощью 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
i.e CPython
. Другие реализации могут отличаться поведением. Короче говоря, не зависеть от него.