Оператор '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) должно быть равным. В сравнении с == работает как положено.

2 ответа

Решение

Это связано с тем, как 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(), литерал с плавающей точкой просто возвращается (с коротким замыканием), как и в той же ссылке, так как нет необходимости создавать экземпляр нового с плавающей точкой из существующего с плавающей точкой:

>>> 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 Fasarakis Hilliard по объектам кода. Даже документ Python включает в себя раздел об этом поведении:

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

Если 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 соответственно.

В результате возвращаемые объекты отличаются id s (который используется для проверки личности с 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 , Другие реализации могут демонстрировать другое поведение. Короче, не зависите от этого.

Другие вопросы по тегам