2012-03-28 44 views
12

NaN được xử lý hoàn hảo khi tôi kiểm tra sự hiện diện của nó trong danh sách hoặc bộ. Nhưng tôi không hiểu làm thế nào. [UPDATE: không có nó không; nó được báo cáo là hiện tại nếu tìm thấy thể hiện NaN giống hệt nhau; nếu chỉ có trường hợp không giống hệt nhau của NaN được tìm thấy, nó được báo cáo như vắng mặt.]Kiểm tra sự hiện diện NaN trong thùng chứa

  1. Tôi nghĩ sự hiện diện trong danh sách được kiểm tra bằng cách bình đẳng, vì vậy tôi mong đợi NaN để không bị phát hiện kể từ NaN! = NaN.

  2. băm (NaN) và băm (0) là cả 0. Làm cách nào để từ điển và tập hợp cho NaN và 0 cách nhau?

  3. Có an toàn để kiểm tra sự hiện diện của NaN trong một thùng chứa tùy ý bằng cách sử dụng toán tử in không? Hoặc là nó thực hiện phụ thuộc?

Câu hỏi của tôi là về Python 3.2.1; nhưng nếu có bất kỳ thay đổi nào hiện có/lên kế hoạch trong các phiên bản sau, tôi cũng muốn biết điều đó.

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? 

Lưu ý rằng nếu tôi thêm một thể hiện của một lớp người dùng định nghĩa để một list, và sau đó kiểm tra để ngăn chặn, phương pháp của dụ __eq__ được gọi là (nếu định nghĩa) - ít nhất là trong CPython. Đó là lý do tại sao tôi giả định rằng ngăn chứa list được kiểm tra bằng cách sử dụng toán tử ==.

EDIT:

câu trả lời mỗi La Mã, nó có vẻ như __contains__ cho list, tuple, set, dict cư xử theo một cách rất lạ:

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

tôi nói 'lạ' vì tôi didn' t thấy nó giải thích trong tài liệu (có lẽ tôi đã bỏ lỡ nó), và tôi nghĩ rằng đây là một cái gì đó mà không nên để lại như là một sự lựa chọn thực hiện.

Tất nhiên, một đối tượng NaN có thể không giống nhau (theo nghĩa là id) đối với một đối tượng NaN khác. (Điều này không thực sự đáng ngạc nhiên; Python không bảo đảm danh tính đó. Thực ra, tôi chưa bao giờ thấy CPython chia sẻ một cá thể NaN được tạo ra ở những nơi khác nhau, mặc dù nó chia sẻ một thể hiện của một số nhỏ hoặc một chuỗi ngắn.) thử nghiệm cho sự hiện diện NaN trong một thùng chứa tích hợp là không xác định.

Điều này rất nguy hiểm và rất tinh tế. Ai đó có thể chạy đúng mã tôi đã trình bày ở trên và kết luận không chính xác rằng việc kiểm tra thành viên NaN là an toàn khi sử dụng in.

Tôi không nghĩ rằng có giải pháp hoàn hảo cho vấn đề này. Một cách tiếp cận rất an toàn là đảm bảo rằng NaN không bao giờ được thêm vào các thùng chứa tích hợp. (Đó là một nỗi đau để kiểm tra rằng trong tất cả các mã ...)

lựa chọn khác là xem ra đối với trường hợp có thể có in NaN ở phía bên trái, và trong những trường hợp như vậy, thử nghiệm cho thành viên NaN riêng, sử dụng math.isnan() . Ngoài ra, các hoạt động khác (ví dụ: giao lộ được đặt) cũng cần phải tránh hoặc viết lại.

+0

Bottonline: để sử dụng an toàn: bất kỳ (math.isnan (phần tử) cho phần tử trong list_) – jsbueno

+0

@jsbueno: Yup ... Nhưng điều đó không giúp được gì với vấn đề giao cắt được đặt; cũng không xử lý trường hợp của 'cho x trong cont1: nếu x trong cont2 làm một cái gì đó' ... Tôi muốn nói dòng dưới cùng là" rất sợ, và chỉ hy vọng bạn không bỏ qua một cái gì đó " – max

+0

Nó thắng ' t giúp - và bạn phải đồng ý không có giải pháp dễ dàng. Bạn có thể sử dụng vòng lặp ở trên để chuyển đổi bất kỳ NaN nào thành chuỗi đọc "NaN" - những thứ này sẽ so sánh một cách vô cùng. – jsbueno

Trả lời

3

Câu hỏi # 1: tại sao NaN tìm thấy trong một container khi nó là một đối tượng giống hệt nhau.

Từ documentation:

Đối với các loại container như danh sách, tuple, thiết lập, frozenset, dict, hoặc collections.deque, khái niệm x trong y là tương đương với bất kỳ (x là e hoặc x == e cho e trong y).

Đây chính xác là những gì tôi quan sát với NaN, vì vậy mọi thứ đều ổn. Tại sao lại là quy tắc này? Tôi nghi ngờ đó là vì dict/set muốn báo cáo thành thật rằng nó chứa một đối tượng nhất định nếu đối tượng đó thực sự ở trong đó (ngay cả khi __eq__() vì bất kỳ lý do nào chọn báo cáo rằng đối tượng không bằng chính nó).

Câu hỏi # 2: tại sao giá trị băm cho NaN giống với 0?

Từ documentation:

gọi bằng built-in hàm băm() và cho các hoạt động trên các thành viên của bộ sưu tập băm bao gồm bộ, frozenset, và dict. băm() phải trả lại một số nguyên. Thuộc tính bắt buộc duy nhất là các đối tượng so sánh bằng nhau có cùng giá trị băm; nó được khuyến khích bằng cách nào đó kết hợp với nhau bằng cách nào đó (ví dụ: sử dụng độc quyền hoặc) giá trị băm cho các thành phần của đối tượng cũng đóng một phần so với đối tượng .

Lưu ý rằng yêu cầu chỉ theo một hướng; các đối tượng có cùng giá trị băm không phải bằng nhau! Lúc đầu, tôi nghĩ đó là một lỗi đánh máy, nhưng sau đó tôi nhận ra rằng nó không phải. Hash va chạm xảy ra anyway, ngay cả với mặc định __hash__() (xem một lời giải thích tuyệt vời here). Các thùng chứa xử lý xung đột mà không gặp bất kỳ sự cố nào. Họ làm, tất nhiên, cuối cùng sử dụng các nhà điều hành == để so sánh các yếu tố, do đó họ có thể dễ dàng kết thúc với nhiều giá trị của NaN, miễn là họ không giống nhau! Hãy thử điều này:

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

Vì vậy, mọi thứ đều được làm tài liệu. Nhưng ... nó rất nguy hiểm!Có bao nhiêu người biết rằng nhiều giá trị của NaN có thể sống cùng nhau trong một quyết định? Bao nhiêu người sẽ thấy điều này dễ dàng để gỡ lỗi? ..

Tôi khuyên bạn nên tạo NaN một thể hiện của một lớp con của float không hỗ trợ băm và do đó không thể được thêm vào số set/dict. Tôi sẽ gửi nó đến các ý tưởng python.

Cuối cùng, tôi tìm thấy một sai lầm trong tài liệu here:

Đối với lớp người dùng định nghĩa mà không xác định __contains__() nhưng xác định __iter__(), x in y là đúng nếu một số giá trị z với x == z là sản xuất trong khi lặp lại trên y. Nếu một ngoại lệ được nâng lên trong quá trình lặp lại , thì dường như là in đã tăng ngoại lệ đó.

Cuối cùng, giao thức lặp kiểu cũ được thử: nếu một lớp định nghĩa __getitem__(), x in y là đúng nếu và chỉ nếu có một chỉ số nguyên không âm ix == y[i], và tất cả các chỉ số số nguyên thấp làm không nêu ra ngoại lệ IndexError. (Nếu bất kỳ ngoại lệ nào khác được nêu ra, nó giống như nếu in nêu lên ngoại lệ đó).

Bạn có thể nhận thấy rằng không có đề cập đến số is tại đây, không giống như các vùng chứa được tích hợp sẵn. Tôi đã rất ngạc nhiên bởi điều này, vì vậy tôi cố gắng:

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

Như bạn thấy, danh tính được kiểm tra đầu tiên, trước khi == - phù hợp với các container built-in. Tôi sẽ gửi báo cáo để sửa các tài liệu.

+0

Bạn có thể quan tâm đến vấn đề này bugs.python.org: http://bugs.python.org/issue11945 –

2

Tôi không thể repro bạn tuple/set trường hợp sử dụng float('nan') thay vì NaN.

Vì vậy, tôi cho rằng nó chỉ hoạt động vì id(NaN) == id(NaN), tức làkhông có thực tập cho NaN đối tượng:

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

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

Tôi tin tuple/bộ tra cứu có một số tối ưu hóa liên quan đến so sánh các đối tượng tương tự.

Trả lời câu hỏi của bạn - đường may không an toàn để chuyển tiếp trên nhà cung cấp in trong khi kiểm tra sự hiện diện của NaN. Tôi khuyên bạn nên sử dụng None, nếu có thể.


Chỉ cần nhận xét. __eq__ không có gì để làm với is tuyên bố, và trong quá trình tra cứu so sánh các id đối tượng dường như xảy ra trước khi bất kỳ sự so sánh giá trị:

>>> 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 điều này thật kỳ lạ. 'float ('nan')' là kiểu 'float' và' float' định nghĩa '__eq__', vì vậy tôi không hiểu Python sẽ quay trở lại bằng cách sử dụng' id' thay vì kiểm tra sự bình đẳng như thế nào. Hơn nữa, khi tôi làm theo ví dụ của bạn, tôi thấy rằng 'float (' nan ') trong {float (' nan '), 1} 'là' False'; do đó, có vẻ như 'set' sử dụng' id' làm hàm băm thay vì 'băm'. Một lần nữa, nó lạ từ 'float ('nan') .__ hash__' tồn tại (và ước lượng là' 0'). Nó đi mà không nói rằng tôi đồng ý 100% với câu trả lời của bạn về 'in' là không an toàn cho NaNs! :) – max

+0

@max xem câu trả lời mở rộng. –

+0

Tôi hiểu (và mong đợi) rằng 'is' không gọi' __eq__'. Mối quan tâm của tôi là 'is' được sử dụng thay cho' __eq__' để kiểm tra nếu một đối tượng có trong danh sách. 'is' có thể đánh giá sai trên các chuỗi hoặc số giống hệt nhau, vì vậy nó không phải là cách tiếp cận đúng cho các bài kiểm tra thành viên. – max

Các vấn đề liên quan