2015-03-23 9 views
7

natknąłem się na ten ciekawy przykład dzisiajJak Python 2.7 porównać przedmioty wewnątrz listy

class TestableEq(object): 
    def __init__(self): 
     self.eq_run = False 
    def __eq__(self, other): 
     self.eq_run = True 
     if isinstance(other, TestableEq): 
      other.eq_run = True 
     return self is other 

>>> eq = TestableEq() 
>>> eq.eq_run 
False 
>>> eq == eq 
True 
>>> eq.eq_run 
True 
>>> eq = TestableEq() 
>>> eq is eq 
True 
>>> eq.eq_run 
False 
>>> [eq] == [eq] 
True 
>>> eq.eq_run # Should be True, right? 
False 
>>> (eq,) == (eq,) # Maybe with tuples? 
True 
>>> eq.eq_run 
False 
>>> {'eq': eq} == {'eq': eq} # dicts? 
True 
>>> eq.eq_run 
False 
>>> import numpy as np # Surely NumPy works as expected 
>>> np.array([eq]) == np.array([eq]) 
True 
>>> eq.eq_run 
False 

Wydaje się więc, że porównania wewnątrz kontenerów działa inaczej w Pythonie. Spodziewam się, że wywołanie == będzie korzystać z implementacji każdego obiektu __eq__, w przeciwnym razie o co chodzi? Dodatkowo

class TestableEq2(object): 
    def __init__(self): 
     self.eq_run = False 
    def __eq__(self, other): 
     self.eq_run = True 
     other.eq_run = True 
     return False 

>>> eq = TestableEq2() 
>>> [eq] == [eq] 
True 
>>> eq.eq_run 
False 
>>> eq == eq 
False 
>>> eq.eq_run 
True 

Czy to znaczy, że Python używa is z poziomu implementacji kontenera z __eq__ zamiast? Czy istnieje sposób obejścia tego?

Moim przypadkiem użycia jest to, że buduję strukturę danych dziedzicząc z niektórych ABC z collections i chcę napisać testy, aby upewnić się, że moja struktura zachowuje się poprawnie. Pomyślałem, że łatwo byłoby wstrzyknąć wartość, która została zarejestrowana podczas porównywania, ale ku mojemu zdziwieniu test się nie powiódł, gdy sprawdzałem, czy to porównanie miało miejsce.

EDYCJA: Należy wspomnieć, że jest to na Pythonie 2.7, ale widzę to samo zachowanie na 3.3.

+1

Jeśli 'x to y', nie ma powodu, aby wywoływać' x == y'. Wyobrażam sobie, że Python bierze ten skrót. Można to zweryfikować/obalić, tworząc w testach 'eq1' * i * an' eq2', a następnie używając '[eq1] == [eq2]'. – user2864740

+1

@ user2864740 Jeśli chciałbym, aby Python używał 'is', użyłbym go. Nie chcę po prostu porównywać, czy obiekty są takie same, chcę poznać wynik '=='. Jest duża różnica i jestem zaskoczony, że Python wydaje się źle to rozumieć. Naprawdę sprowadza się do operacyjnej różnicy między '[eq] == [eq]' a '[eq1] == [eq2]'. W pierwszym przypadku Python zwróci "True" bez względu na implementację '__eq__', ale w drugim przypadku wywoła' eq1 .__ eq __ (eq2) '. Dlaczego różne wdrożenia? Dlaczego nie pozwolić mi wybrać, w jaki sposób należy porównywać elementy wewnątrz kontenera? – bheklilr

+0

Nie ma znaczenia, co ma robić Python. – user2864740

Odpowiedz

13

Podstawowa implementacja CPython pominie kontrolę równości (==) dla pozycji na liście, jeśli elementy są identyczne (is).

CPython używa tego jako optymalizacji zakładając, że tożsamość oznacza równość.

Jest to udokumentowane w PyObject_RichCompareBool, który służy do porównywania elementów:

Uwaga: Jeśli o1 i o2 to ten sam obiekt, PyObject_RichCompareBool() zawsze zwraca 1 dla Py_EQ i 0 dla Py_NE.

z realizacji listobject.c:

/* Search for the first index where items are different */ 
for (i = 0; i < Py_SIZE(vl) && i < Py_SIZE(wl); i++) { 
    int k = PyObject_RichCompareBool(vl->ob_item[i], 
            wl->ob_item[i], Py_EQ); 
    // k is 1 if objects are the same 
    // because of RichCmopareBool's behaviour 
    if (k < 0) 
     return NULL; 
    if (!k) 
     break; 
} 

Jak widać ile RichCompareBool jest 1 (True) pozycje nie są sprawdzane.

A z realizacji object.c „s PyObject_RichCompareBool:

/* Quick result when objects are the same. 
    Guarantees that identity implies equality. */ 
if (v == w) { 
    if (op == Py_EQ) 
     return 1; 
    else if (op == Py_NE) 
     return 0; 
} 
// ... actually deep-compare objects 

Aby to przesłonić musisz porównać elementy ręcznie.

+4

Brawo, że idziesz do źródła! – user2864740

+2

Nie sądzę, że może on stać się bardziej konkretny. Dokumentacja wyraźnie stwierdza, że ​​dostaję oczekiwane zachowanie i że '__eq__' jest wywoływane tylko wtedy, gdy obiekty mają różne' id's, a źródło obsługuje to twierdzenie. – bheklilr

4

Jeśli x is y nie ma powodu, aby zadzwonić pod numer x == y, na podstawie umowy z ==. Python bierze ten skrót.

Można to zweryfikować/obalić, tworząc w testach i eq2, a następnie używając [eq1] == [eq2].

Here is as example:

class TestableEq(object): 
    def __init__(self): 
     self.eq_run = False 
    def __eq__(self, other): 
     self.eq_run = True 
     return True  # always assume equals for test 

eq1 = TestableEq() 
eq2 = TestableEq() 
eq3 = TestableEq() 

print [eq1] == [eq2] # True 
print eq1.eq_run  # True - implies e1 == e2 
print eq2.eq_run  # False - but NOT e2 == e1 

print [eq3] == [eq3] # True 
print eq3.eq_run  # False - implies NO e3 == e3 

Gdy elementy są is nie ma == zaangażowany.

Różnicę ze słownikami można wyjaśnić podobnie.

10

testowanie Pythona równości dla sekwencji jest następujący:

    Lists identical? 
       /  \ 
       Y   N 
       /   \ 
      Equal   Same length? 
          /  \ 
          Y   N 
         /   \ 
        Items identical? Not equal 
        /  \ 
        Y   N 
       /   \ 
       Equal  Items equal? 
          /  \ 
          Y   N 
         /   \ 
         Equal  Not equal 

Widać, że równość pozycji w każdej pozycji jest testowany tylko jeśli dwie sekwencje są tej samej długości, ale pozycje w każda pozycja nie jest identyczna. Jeśli chcesz, aby wymusić sprawdza równość być wykorzystywane, trzeba np

all(item1 == item2 for item1, item2 in zip(list1, list2)) 
+2

Jest to bardzo łatwe do zrozumienia, dobra praca. – Shashank

3

Porównując dwie listy, realizacja CPython zwarć porównania członkowskie korzystające równości obiektu (obj1 is obj2), ponieważ według a comment in the code:

/* Quick result when objects are the same. 
    Guarantees that identity implies equality. */ 

Jeśli dwa obiekty nie są dokładnie tym samym obiektem, to cPython porównuje bogatą wartość, używając __eq__, jeśli jest zaimplementowana.

+0

Dziękuję za odpowiedź. Niestety Reut pokonał cię, więc przyjąłem jego odpowiedź, ale twoja wskazuje na te same dowody, które pokazują, jak precyzyjnie działa Python. – bheklilr