NaNs как ключ в словарях - PullRequest
18 голосов
/ 22 июня 2011

Может ли кто-нибудь объяснить мне следующее поведение?

>>> import numpy as np
>>> {np.nan: 5}[np.nan]
5
>>> {float64(np.nan): 5}[float64(np.nan)]
KeyError: nan

Почему это работает в первом случае, а не во втором?Кроме того, я обнаружил, что работает следующее:

>>> a ={a: 5}[a]
float64(np.nan)

Ответы [ 2 ]

33 голосов
/ 22 июня 2011

Проблема здесь в том, что NaN не равен самому себе, как определено в стандарте IEEE для чисел с плавающей запятой:

>>> float("nan") == float("nan")
False

Когда словарь ищет ключ, он примерно это делает:

  1. Вычислить хеш ключа для поиска.

  2. Для каждого ключа в dict с одинаковым хешем проверьте, соответствует ли он ключучтобы посмотреть вверх.Эта проверка состоит из

    a.Проверка идентичности объекта: если ключ в словаре и ключ, который нужно найти, совпадают с объектом, указанным оператором is, ключ найден.

    b.Если первая проверка завершилась неудачно, проверьте равенство с помощью оператора __eq__.

Первый пример выполнен успешно, поскольку np.nan и np.nan являются одним и тем же объектом, поэтому он ненезависимо от того, что они не равны:

>>> numpy.nan is numpy.nan
True

Во втором случае np.float64(np.nan) и np.float64(np.nan) не являются одним и тем же объектом - два вызова конструктора создают два разных объекта:

>>> numpy.float64(numpy.nan) is numpy.float64(numpy.nan)
False

Поскольку объекты также не сравниваются равными, словарь делает вывод, что ключ не найден, и выдает KeyError.

. Вы даже можете сделать это:

>>> a = float("nan")
>>> b = float("nan")
>>> {a: 1, b: 2}
{nan: 1, nan: 2}

ВВ заключение, кажется более разумной идея избегать использования NaN в качестве словарного ключа.

2 голосов
/ 13 февраля 2018

Обратите внимание, что это не так в Python 3.6:

>>> d = float("nan") #object nan
>>> d
nan
>>> c = {"a": 3, d: 4}
>>> c["a"]
3
>>> c[d]
4

Как я понимаю:

c - это словарь, который содержит 3, связанные с «a», и 4, связанные с nan. Изменился внутренний взгляд на словарь в Python 3.6, теперь он сравнивает два указателя, и если они указывают на один и тот же объект, они считают, что равенство сохраняется. В противном случае они сравнивают хеш, если хеш отличается, то это не тот же объект. После этого, если необходимо, ключи сравниваются «вручную».

Это означает, что, хотя IEEE754 указывает, что NAN не равен самому себе:

>>> d == d
False

При поиске в словаре сначала учитываются указатели, а поскольку они указывают на один и тот же объект nan, возвращается 4.

Обратите внимание, что:

>>> e = float("nan")
>>> e == d
False
>>> c[e]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: nan
>>> c[d]
4

Таким образом, не каждая нана указывает на 4, поэтому сохраняется какой-то IEEE754. Это было реализовано, потому что соблюдение стандарта, согласно которому nan никогда не равен себе, снижает эффективность более чем игнорирование стандарта. Именно потому, что вы храните в словаре что-то, к чему у вас больше нет доступа в предыдущих версиях.

...