2011-06-22 7 views
15

¿Alguien me puede explicar el siguiente comportamiento?NaNs como clave en los diccionarios

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

¿Por qué funciona en el primer caso, pero no en el segundo? Además, he encontrado que el siguiente no trabajo:

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

esto siempre será cierto: 'float ('nan')! = Float ('nan')' – JBernardo

Respuesta

27

El problema aquí es que NaN no es igual a sí mismo, como se define en el estándar IEEE para números de punto flotante:

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

Cuando una diccionario busca una clave, hace más o menos esto:

  1. Calcule el hash de la clave que se va a buscar.

  2. Para cada tecla en el dict con el mismo hash, verifique si coincide con la clave que se va a buscar. Esta comprobación se compone de

    a. Comprobación de la identidad del objeto: si la clave del diccionario y la clave que se va a buscar son el mismo objeto que indica el operador is, se encontró la clave.

    b. Si falló la primera comprobación, verifique la igualdad utilizando el operador __eq__.

El primer ejemplo tiene éxito, ya que np.nan y np.nan son el mismo objeto, por lo que no importa que no se comparan iguales:

>>> numpy.nan is numpy.nan 
True 

En el segundo caso, np.float64(np.nan)np.float64(np.nan) y no son el mismo objeto - las dos llamadas a constructores crean dos objetos distintos:

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

Dado que los objetos también lo hacen no se compara igual, el diccionario concluye que la clave no se encuentra y arroja un KeyError.

Incluso puede hacer esto:

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

En conclusión, parece una idea más sano para evitar NaN como una clave de diccionario.

+1

ops ... no lo revisó bien :) – JBernardo

+3

La última afirmación merece mucho más énfasis. – job

+0

¿Existe alguna garantía de que todos los 'float ('nan')' tengan la misma ubicación de memoria, es decir, que 'float ('nan')' sea un singleton? Sin él, incluso usar plain 'float ('nan')' es una mala idea. La misma pregunta sobre 'np.nan'. – max

1

Tenga en cuenta que esto no es el caso más en Python 3.6:

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

Como lo entiendo:

d = objeto nan C es un diccionario que contiene 3 asociado a "a" y 4 asociado a nan pero, debido a la forma en que la búsqueda interna de Python 3.6 en los diccionarios ha cambiado, ahora compara dos punteros, y si apuntan al mismo objeto, consideran que se preserva la igualdad.

Eso significa que aunque:

>>> d == d 
False 

Debido a cómo IEEE754 especifica que NAN no es igual a sí mismo, cuando se esté buscando un diccionario en primer lugar los punteros se tienen en cuenta, y porque apuntan a la misma nan objeto devuelve 4.

Tenga en cuenta también que:

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

así que no todos los puntos nan a 4, por lo que algún tipo de IEEE754 se conserva. Esto se implementó porque respetar el estándar de que nan nunca es igual a sí mismo reduce la eficiencia mucho más que ignorar el estándar. Precisamente porque está almacenando algo en un diccionario al que ya no puede acceder en versiones anteriores.

Cuestiones relacionadas