NaN отлично обрабатывается, когда я проверяю его наличие в списке или наборе. Но я не понимаю, как. [ОБНОВЛЕНИЕ: нет, это не так; он сообщается как присутствующий, если найден идентичный экземпляр NaN; если найдены только неидентичные экземпляры NaN, он считается отсутствующим.]
Я думал, что присутствие в списке проверяется равенством, поэтому я ожидал, что NaN не будет найден, поскольку NaN != NaN.
hash(NaN) и hash(0) равны 0. Как словари и наборы различают NaN и 0?
Безопасно ли проверять наличие NaN в произвольном контейнере с помощью оператора in
? Или это зависит от реализации?
Мой вопрос касается Python 3.2.1; но если есть какие-либо изменения, существующие / планируемые в будущих версиях, я тоже хотел бы знать об этом.
NaN = float('nan')
print(NaN != NaN) # True
print(NaN == NaN) # False
list_ = (1, 2, NaN)
print(NaN in list_) # True; works fine but how?
set_ = {1, 2, NaN}
print(NaN in set_) # True; hash(NaN) is some fixed integer, so no surprise here
print(hash(0)) # 0
print(hash(NaN)) # 0
set_ = {1, 2, 0}
print(NaN in set_) # False; works fine, but how?
Обратите внимание, что если я добавляю экземпляр пользовательского класса в список list
, а затем проверяю включение, вызывается метод экземпляра __eq__
(если он определен) - в по крайней мере в CPython. Вот почему я предположил, что включение list
проверяется с помощью оператора ==
.
РЕДАКТИРОВАТЬ:
Согласно ответу Романа, может показаться, что __contains__
for list
, tuple
, set
, dict
ведет себя очень странно:
def __contains__(self, x):
for element in self:
if x is element:
return True
if x == element:
return True
return False
Я говорю «странно», потому что не видел объяснения этого в документации (может быть, пропустил), и я думаю, что это то, что не следует оставлять как выбор реализации.
Конечно, один объект NaN может не быть идентичным (в смысле id
) другому объекту NaN. (В этом нет ничего удивительного, Python не гарантирует такой идентичности. На самом деле, я никогда не видел, чтобы CPython совместно использовал экземпляр NaN, созданный в разных местах, даже если он совместно использует экземпляр небольшого числа или короткой строки.) Это означает, что проверка наличия NaN во встроенном контейнере не определена.
Это очень опасно и очень тонко. Кто-то может запустить тот самый код, который я показал выше, и сделать неверный вывод, что безопасно проверять принадлежность к NaN, используя в
.
Я не думаю, что существует идеальное решение этой проблемы. Один, очень безопасный подход, состоит в том, чтобы гарантировать, что NaN никогда не добавляются во встроенные контейнеры. (Было сложно проверять это по всему коду...)
Другая альтернатива - следить за случаями, когда в
может быть NaN с левой стороны, и в таких случаях проверять NaN членство отдельно, используя math.isnan()
. Кроме того, другие операции (например, установка пересечения) также должны быть исключены или переписаны.