"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. Другие реализации могут отличаться поведением. Короче говоря, не зависеть от него.