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