2012-03-28 6 views
12

NaN se maneja perfectamente cuando verifico su presencia en una lista o un conjunto. Pero no entiendo cómo. [ACTUALIZACIÓN: no, no lo es; se informa como presente si se encuentra la instancia idéntica de NaN; si se encuentran únicos casos no idénticos de NaN, se ha informado como ausente.]Comprobación de presencia de NaN en un contenedor

  1. pensé presencia en una lista es probado por la igualdad, por lo que espera NaN a no ser encontrado desde NaN! = NaN.

  2. hash (NaN) y hash (0) son 0. ¿En qué se diferencian los diccionarios y los juegos de NaN y 0?

  3. ¿Es seguro verificar la presencia de NaN en un contenedor arbitrario usando el operador in? ¿O depende de la implementación?

Mi pregunta es acerca de Python 3.2.1; pero si hay algún cambio existente/planificado en futuras versiones, me gustaría saberlo también.

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? 

Tenga en cuenta que si añado una instancia de una clase definida por el usuario a un list, y a continuación, comprobar para la contención, se llama al método de la instancia __eq__ (si está definida) - por lo menos en CPython. Es por eso que asumí que la contención list se prueba usando el operador ==.

EDIT:

respuesta de Per romana, parecería que __contains__ para list, tuple, set, dict se comporta de una manera muy extraña:

def __contains__(self, x): 
    for element in self: 
    if x is element: 
     return True 
    if x == element: 
     return True 
    return False 

digo 'extraño' porque yo no' T verlo explicado en la documentación (tal vez me lo perdí), y creo que esto es algo que no debería dejarse como una opción de implementación.

Por supuesto, un objeto NaN puede no ser idéntico (en el sentido de id) a otro objeto NaN. (Esto no es realmente sorprendente, Python no garantiza esa identidad. De hecho, nunca vi a CPython compartir una instancia de NaN creada en diferentes lugares, aunque comparte una instancia de un número pequeño o una cadena corta.) Esto significa que las pruebas de presencia de NaN en un contenedor integrado no están definidas.

Esto es muy peligroso y muy sutil. Alguien podría ejecutar el mismo código que mostré anteriormente y concluir de manera incorrecta que es seguro probar la membresía de NaN usando in.

No creo que haya una solución perfecta a este problema. Un enfoque muy seguro es garantizar que los NaN nunca se agreguen a los contenedores incorporados. (Es un dolor comprobar que todo el código ...)

Otra alternativa es tener cuidado con los casos en que in podría tener NaN en el lado izquierdo, y en tales casos, probar la membresía NaN por separado, utilizando math.isnan() . Además, otras operaciones (p. Ej., Establecer intersección) también deben evitarse o reescribirse.

+0

Bottonline: para estar en el lado seguro use: any (math.isnan (element) for element in list_) – jsbueno

+0

@jsbueno: Sip ... Pero eso no ayuda con el problema de intersección establecida; ni maneja el caso de 'para x en cont1: si x en cont2 hace algo' ... Diría que la conclusión es" ten mucho miedo, y solo espero que no pases por alto algo " – max

+0

Ganó ' t ayuda, y tiene que aceptar que no hay una solución fácil. Puede usar el ciclo superior para convertir cualquier NaN a cadenas que digan "NaN"; se compararán de forma no equitativa. – jsbueno

Respuesta

3

Pregunta # 1: ¿por qué se NaN encontrado en un contenedor cuando se trata de un objeto idéntico.

Desde el documentation:

Para los tipos de contenedores tales como lista, tupla, conjunto, frozenset, dict, o collections.deque, la expresión x en la que y es equivalente a cualquier (x es E o x == e para e en y).

Esto es precisamente lo que observo con NaN, así que todo está bien. ¿Por qué esta regla? Sospecho que es porque un dict/set quiere informar honestamente que contiene un determinado objeto si ese objeto está realmente en él (incluso si __eq__() por cualquier razón decide informar que el objeto no es igual a sí mismo).

Pregunta # 2: ¿por qué el valor de hash para NaN es el mismo que para 0?

Desde el documentation:

Llamado de Hash función incorporada() y para las operaciones en los miembros de colecciones hash incluyendo conjunto, frozenset y dict. hash() debe devolver un número entero. La única propiedad requerida es que los objetos que se comparan son iguales tienen el mismo valor hash; se recomienda combinar de alguna manera (por ejemplo, utilizando exclusivamente o) los valores hash para los componentes del objeto que también desempeñan un papel en comparación con los objetos .

Tenga en cuenta que el requisito es solo en una dirección; ¡Los objetos que tienen el mismo hash no tienen por qué ser iguales! Al principio pensé que era un error tipográfico, pero luego me di cuenta de que no era así. Las colisiones hash suceden de todos modos, incluso con __hash__() predeterminado (ver una excelente explicación here). Los contenedores manejan las colisiones sin ningún problema. Por supuesto, utilizan el operador == para comparar elementos, por lo tanto, pueden terminar fácilmente con múltiples valores de NaN, ¡siempre que no sean idénticos! Pruebe esto:

>>> nan1 = float('nan') 
>>> nan2 = float('nan') 
>>> d = {} 
>>> d[nan1] = 1 
>>> d[nan2] = 2 
>>> d[nan1] 
1 
>>> d[nan2] 
2 

Así que todo funciona como está documentado. Pero ... ¡es muy muy peligroso!¿Cuántas personas sabían que los valores múltiples de NaN podrían vivir uno junto al otro en un dict? Cuántas personas se encontrarían tan fácil de depurar? ..

Yo recomendaría hacer NaN una instancia de una subclase de float que no admite hash y por lo tanto no puede ser añadido accidentalmente a un set/dict. Voy a enviar esto a python-ideas.

Finalmente, encontré un error en la documentación here:

Para las clases definidas por el usuario que no definen __contains__() Pero, ¿ definen __iter__(), x in y es cierto si algún valor z con x == z es mientras producido iterando sobre y. Si se produce una excepción durante la iteración , es como si in hubiera planteado esa excepción.

Por último, el protocolo de iteración de estilo antiguo se trató: si una clase define __getitem__(), x in y es cierto si y sólo si hay un no negativo índice entero i tal que x == y[i], y todos los índices enteros inferior hacer no aumentar la excepción IndexError. (Si se produce alguna otra excepción, es como si in hubiera planteado esa excepción).

Puede notar que aquí no se menciona is, a diferencia de los contenedores integrados. Me sorprendió por esto, así que he intentado:

>>> nan1 = float('nan') 
>>> nan2 = float('nan') 
>>> class Cont: 
... def __iter__(self): 
...  yield nan1 
... 
>>> c = Cont() 
>>> nan1 in c 
True 
>>> nan2 in c 
False 

Como se puede ver, la identidad se comprueba en primer lugar, antes de == - consistente con los contenedores incorporados. Presentaré un informe para corregir los documentos.

+0

Puede que le interese este número de bugs.python.org: http://bugs.python.org/issue11945 –

2

No puedo reprogramar las cajas de tupla/conjunto usando float('nan') en lugar de NaN.

Supongo que funcionó solo porque id(NaN) == id(NaN), es decirno hay internado para NaN objetos:

>>> NaN = float('NaN') 
>>> id(NaN) 
34373956456 
>>> id(float('NaN')) 
34373956480 

Y

>>> NaN is NaN 
True 
>>> NaN is float('NaN') 
False 

creo búsquedas tupla/conjunto tiene alguna optimización en relación con la comparación de los mismos objetos.

Respondiendo a su pregunta: no es seguro retransmitir en el operador in mientras comprueba la presencia de NaN. Yo recomendaría usar None, si es posible.


A un comentario. __eq__ no tiene nada que ver con is declaración, y durante las búsquedas de comparación de identificadores de objetos parecen ocurrir antes de cualquier comparaciones de valor:

>>> class A(object): 
...  def __eq__(*args): 
...    print '__eq__' 
... 
>>> A() == A() 
__eq__   # as expected 
>>> A() is A() 
False   # `is` checks only ids 
>>> A() in [A()] 
__eq__   # as expected 
False 
>>> a = A() 
>>> a in [a] 
True   # surprise! 
+0

Wow esto es raro. 'float ('nan')' es de tipo 'float', y' float' define '__eq__', así que no entiendo cómo Python recurriría a' id' para verificar la igualdad. Además, cuando seguí tu ejemplo, encontré que 'float ('nan') en {float ('nan'), 1}' es 'False'; por lo que parece que 'set' usa' id' como una función hash en lugar de 'hash'. De nuevo, es extraño ya que 'float ('nan') .__ hash__' existe (y se evalúa a' 0'). ¡Huelga decir que estoy 100% de acuerdo con su respuesta acerca de que 'in' no es seguro para NaNs! :) – max

+0

@max ver respuesta ampliada. –

+0

Entiendo (y espero) que 'is' no llame a' __eq__'. Mi preocupación es que 'is' se use en lugar de' __eq__' para probar si un objeto está presente en una lista. 'is' puede evaluar False en cadenas o números idénticos, por lo que no es el enfoque correcto para las pruebas de membresía. – max