Проверка наличия NaN в контейнере

NaN отлично обрабатывается, когда я проверяю его наличие в списке или наборе. Но я не понимаю, как. [ОБНОВЛЕНИЕ: нет, это не так; он сообщается как присутствующий, если найден идентичный экземпляр NaN; если найдены только неидентичные экземпляры NaN, он считается отсутствующим.]

  1. Я думал, что присутствие в списке проверяется равенством, поэтому я ожидал, что NaN не будет найден, поскольку NaN != NaN.

  2. hash(NaN) и hash(0) равны 0. Как словари и наборы различают NaN и 0?

  3. Безопасно ли проверять наличие 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(). Кроме того, другие операции (например, установка пересечения) также должны быть исключены или переписаны.

12
задан max 5 April 2012 в 06:52
поделиться