NaNs como clave en los diccionarios

¿Alguien puede explicarme el siguiente comportamiento?

>>> 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, encontré que lo siguiente funciona:

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

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 un diccionario busca una clave, aproximadamente hace esto:

  1. Calcula el hash de la clave a buscar.

  2. Para cada clave en el dictado con el mismo hash, verifique si coincide con la clave a buscar. Esta comprobación consiste en

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

    segundo. Si la primera verificación falló, verifique la igualdad usando 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 comparen iguales:

 >>> numpy.nan is numpy.nan True 

En el segundo caso, np.float64(np.nan) y np.float64(np.nan) no son el mismo objeto; las dos llamadas de constructor crean dos objetos distintos:

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

Dado que los objetos tampoco se comparan de la misma manera, el diccionario concluye que la clave no se encuentra y lanza un KeyError .

Incluso puedes hacer esto:

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

En conclusión, parece una idea sensata evitar la NaN como clave de diccionario.

Tenga en cuenta que este ya no es el caso en Python 3.6:

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

Como yo lo entiendo:

c es un diccionario que contiene 3 asociados a “a” y 4 asociados a nan. La forma en que Python 3.6 busca internamente el diccionario ha cambiado, ahora compara dos punteros, y si apuntan al mismo objeto, consideran que se mantiene la igualdad. De lo contrario, comparan el hash, si el hash es diferente, entonces no es el mismo objeto. Después de todo eso, si es necesario, las teclas se comparan “manualmente”.

Eso significa que aunque IEEE754 especifica que NAN no es igual a sí mismo:

 >>> d == d False 

Al buscar un diccionario primero, se toman en cuenta los punteros y, como apuntan al mismo objeto nan, devuelve 4.

Tenga en cuenta también que:

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

Así que no todos los puntos nan a 4, por lo que se conserva algún tipo de IEEE754. 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.