2012-03-28 2 views
12

NaN은 목록 또는 세트에서 해당 존재를 확인하면 완벽하게 처리됩니다. 그러나 나는 어떻게 이해하지 못한다. [업데이트 : 아니 아니에요; NaN의 동일한 인스턴스가 발견되면 (자) 현재로서보고됩니다. NaN이의 동일하지 않은 경우가 발견되면, 컨테이너에 NaN 존재 여부 확인

  1. 내가 평등에 따라 시험 할 때,리스트의 존재를 생각]. 결석으로보고, 그래서 나는 NaN이가 NaN 이후 찾을 수 없습니다 것으로 예상! = NaN가.

  2. 해시 (NaN)와 해시 (0)는 모두 0입니다. 사전과 집합이 NaN과 0을 어떻게 구별합니까?

  3. in 연산자를 사용하여 임의의 컨테이너에서 NaN 존재를 확인하는 것이 안전합니까? 아니면 구현에 의존 하는가?

내 질문에 대한 파이썬 3.2.1; 그러나 향후 버전에 기존/계획된 변경 사항이있는 경우이를 알고 싶습니다. 적어도 CPython의에 - 나는 list에 사용자 정의 클래스의 인스턴스를 추가 한 다음 봉쇄를 확인하는 경우 (정의 된 경우), 인스턴스의 __eq__ 메서드가 호출되는 것을

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 봉쇄가 == 연산자를 사용하여 테스트되었다고 가정합니다.

편집 :

당 로마의 대답은, 그것은 list는, 아주 이상한 방법으로 tuple, set, dict 동작합니다이 __contains__위한 것으로 보인다 : I didn를하기 때문에

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

내가 '이상한'말 ' 설명서에 설명 된 내용 (어쩌면 내가 놓친 것)이 보이고 이것이 구현 선택으로 남겨서는 안되는 것입니다.

물론 하나의 NaN 객체는 다른 NaN 객체와 동일하지 않을 수도 있습니다 (id 의미). (사실 놀랍지는 않지만 파이썬은 그러한 신원을 보증하지 않습니다. 사실 CPython은 작은 수 또는 짧은 문자열의 인스턴스를 공유하지만 다른 장소에서 생성 된 NaN의 인스턴스를 공유하지 못했습니다.) 내장 컨테이너에 NaN이 있는지 테스트하는 것은 정의되지 않습니다.

이것은 매우 위험하고 매우 미묘합니다. 위의 코드를 실행하면 in을 사용하여 NaN 멤버십을 테스트하는 것이 안전하다고 잘못 결론 지을 수 있습니다.

나는이 문제에 대한 완벽한 해결 방법이 없다고 생각합니다. 하나의 매우 안전한 접근 방법은 NaN이 내장 컨테이너에 추가되지 않도록하는 것입니다. (코드 전체에서 확인하는 것이 고통 스럽습니다 ...)

in의 왼쪽에 NaN이있을 수 있으므로 다른 방법으로는 math.isnan()을 사용하여 NaN 멤버십을 별도로 테스트하십시오. . 또한, 다른 작업 (예 : 교차 설정)도 피해야하거나 다시 작성해야합니다.

+0

Bottonline : 안전한쪽에 사용 : any (목록의 요소에 대한 math.isnan (요소)) – jsbueno

+0

@jsbueno : 예 ...하지만 교차로 문제는 도움이되지 않습니다. 'cont1에서 x에 대해 : cont2에서 x가 뭔가를하면'의 경우도 처리하지 않습니다 ... 결론은 "매우 두려워하고, 당신이 뭔가를 간과하지 않기를 바랄 것"이라고 말하고 싶습니다. – max

+0

그것은 ' 도움을 청하십시오. 쉬운 해결책이 없다는 것에 동의해야합니다. 위의 루프를 사용하여 임의의 NaN을 "NaN"문자열로 변환 할 수 있습니다. 이는 비교할 수 없을 정도로 비교할 것입니다. – jsbueno

답변

3

질문 # 1 : 그것은 동일한 개체의 경우 NaN의 컨테이너에서 발견되는 이유. 가입일

documentation : 이러한 설정에서, 튜플, frozenset, 딕셔너리, 또는 collections.deque 같은 컨테이너 유형의

는 Y에서 식 (X)은 임의의 (X에 해당 전자 또는 y는 e에 대해 x == e).

이것은 정확하게 NaN에서 관찰 한 것이므로 모든 것이 정상입니다. 왜이 규칙? dict/set은 어떤 이유에서 건 객체가 자신과 동등하지 않다는 것을보고 싶은 경우에도이더라도 그 객체가 실제로 있으면 그 객체가 포함되어 있다고 솔직하게보고하기를 원합니다.

질문 # 2 : NaN의 해시 값이 0과 같은 이유는 무엇입니까? documentation 가입일

:

내장 함수에 의해 호출 해시

() 및 세트 frozenset 및 딕셔너리 포함 해시 컬렉션 멤버에 작업. 해시() 은 정수를 반환해야합니다. 유일한 필수 속성은 같은 개체를 비교하는 개체가 동일한 해시 값을가집니다. 어떻게 든 개체의 비교에서 일부를 재생하는 개체의 구성 요소에 대한 해시 값을 함께 (예 : 배타적 또는를 사용하여) 함께 혼합하는 것이 좋습니다 ( ).

요구 사항은 한 방향으로 만 적용됩니다. 동일한 해시를 가진 객체는 동일 할 필요가 없습니다! 처음에는 오타라고 생각했지만, 그렇지 않다는 것을 깨달았습니다. 어쨌든 기본값 인 __hash__()으로 해시 충돌이 발생합니다 (우수 설명 here 참조). 컨테이너는 아무런 문제없이 충돌을 처리합니다. 그들은 궁극적으로 요소를 비교하기 위해 궁극적으로 == 연산자를 사용합니다. 따라서 동일하지 않은 한 여러 값의 NaN으로 쉽게 끝날 수 있습니다! 사용해보기 :

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

모든 것이 문서화 된대로 작동합니다. 하지만 ... 아주 위험합니다!얼마나 많은 사람들이 여러 개의 NaN 값이 서로 함께 살 수 있다는 것을 알고 있었습니까? 많은 사람들이 디버깅이 쉽게 찾아 낼 것입니다 어떻게? ..

나는 NaN이에게 실수로 set/dict에 추가 할 수 없습니다 따라서 해싱을 지원하지 않는 float의 서브 클래스의 인스턴스를 만들기 위해 추천 할 것입니다. 이것을 python-ideas에 제출하겠습니다. x == z 일부 값 z이 생산하면서 경우

__contains__()을 정의하지 않지만 이 __iter__()을 정의 할 사용자 정의 클래스의 경우

, x in y가 true :

마지막으로, 나는 문서 here에서 실수를 발견 y을 반복합니다. 반복 중에 예외가 발생하면 마치 in이 예외를 발생시킨 것입니다. 클래스가 __getitem__()을 정의하는 경우 음수가 아닌 정수 인덱스 i 등이 x == y[i]이 있고 경우에만 경우 x in y는 사실, 및 그 이하의 모든 정수 인덱스는 을 수행

마지막으로, 이전 스타일의 반복 프로토콜을 시도한다 모금하지 마십시오. IndexError 예외. (다른 예외가 발생한 경우 은 마치 in처럼 해당 예외를 발생시킵니다.

여기에 내장 용기와 달리 is에 대한 언급이 없음을 알 수 있습니다. 나는이 놀랄, 그래서 나는 시도했다 :

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

당신이 볼 수 있듯이

은 신원이 == 전에 먼저 체크 - 내장 용기와 일치. 문서를 수정하는 보고서를 제출하겠습니다.

+0

이 bugs.python.org 문제에 관심이있을 수 있습니다. http://bugs.python.org/issue11945 –

2

NaN 대신 float('nan')을 사용하여 튜플/세트 케이스를 다시 표시 할 수 없습니다.

그래서 나는 단지 id(NaN) == id(NaN), 즉거기에는 NaN 개체에 대한 인턴 :

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

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

그리고

내가 튜플/세트 조회를 믿는다는 같은 객체의 비교에 관한 몇 가지 최적화가 없습니다.

귀하의 질문에 답변 - NaN의 존재 여부를 확인하면서 in 연산자를 중계하는 것이 안전하지 않은지 확인하십시오. 가능한 경우 None을 사용하는 것이 좋습니다.


그냥 의견. __eq__is 문을 아무 상관이 없으며, 조회시 객체의 식별자의 비교는 이전 값의 비교에 일어날 것 :

>>> 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

와우 이상합니다. 'float ('nan')'은'float' 타입이고'float'는'__eq__'을 정의합니다. 그래서 파이썬이 동등성을 검사하기 위해'id'를 어떻게 사용하는지 이해하지 못합니다. 게다가, 당신의 예를 따를 때, {float ('nan'), 1}의'float ('nan')은'False' 인 것을 발견했습니다; 그래서'set'은'hash' 대신에'id'를 해시 함수로 사용합니다. 다시 말하지만,'float ('nan') .__ hash__'가 존재하고 ('0'으로 평가되기 때문에) 이상합니다. NaN에 대해 안전하지 않은'in '에 대한 답으로 100 % 동의한다는 것은 말할 필요도 없습니다. :) – max

+0

@max 확장 된 답변을 참조하십시오. –

+0

저는'is'가'__eq__'를 호출하지 않는다는 것을 이해합니다. 내 관심사는'__eq__' 대신에'is'가 사용되어 객체가리스트에 존재하는지 테스트하는 것입니다. 'is'는 동일한 문자열이나 숫자에 대해 False로 평가 될 수 있으므로 멤버십 테스트에 올바른 방법이 아닙니다. – max

관련 문제