2016-10-24 64 views
55

Dlaczego wywołanie funkcji set powoduje wymazanie duplikatów, ale przetwarzanie zestawu liter nie ma znaczenia?Ustawienie literału daje inny wynik z wywołania funkcji set

>>> x = Decimal('0') 
>>> y = complex(0,0) 
>>> set([0, x, y]) 
{0} 
>>> {0, x, y} 
{Decimal('0'), 0j} 

(Python 2.7.12. Ewentualnie sama przyczyna jak this podobnym pytaniem)

+0

pokrewne: [Dict/Set analizowaniem Zamówienie Spójność] (https://stackoverflow.com/q/34623846/4279) – jfs

Odpowiedz

53

Zestawy test równości i dopóki istnieją nowe wersje Pythona, kolejność, w jakiej robią to może się różnić w zależności na formularzu podajesz wartości zestawowi, który budujesz, co pokażę poniżej.

Od 0 == x prawda i0 == y to prawda, ale x == y jest fałszywe zachowanie tutaj jest naprawdę niezdefiniowany, ponieważ zakłada się, że zestaw x == y muszą być prawdziwe, jeśli dwa pierwsze testy były zbyt prawdziwe.

Jeśli odwrócić wykazu przekazanego do set(), a następnie pojawi się taki sam efekt jak użycie dosłownym, ponieważ kolejność równości testuje zmiany:

>>> set([y, x, 0]) 
set([0j, Decimal('0')]) 

i taka sama dla odwrócenia dosłowne:

>>> {y, x, 0} 
set([0]) 

co się dzieje jest to, że zestaw dosłowne wartości obciążenia na stosie, a następnie stos wartości są dodawane do nowego zestawu obiektów w odwrotnej kolejności.

Dopóki 0 załadowano pierwszy, dwa inne cele są następnie testowane na 0 już w zestawie. W chwili, jeden z dwóch innych obiektów jest ładowany pierwszy test równości nie i masz dwa obiekty dodania:

>>> {y, 0, x} 
set([Decimal('0'), 0j]) 
>>> {x, 0, y} 
set([0j, Decimal('0')]) 

To ustawienie literały dodać elementy w odwrotnej kolejności jest obecny błąd we wszystkich wersjach Pythona, które obsługują składnię , aż do wersji Python 2.7.12 i 3.5.2. Zostało ono ostatnio naprawione, patrz issue 26020 (część 2.7.13, 3.5.3 i 3.6, z których żadna nie została jeszcze wydana). Jeśli spojrzeć na 2.7.12, można zobaczyć, że BUILD_SET in ceval.c odczytuje stos od góry do dołu:

# oparg is the number of elements to take from the stack to add 
for (; --oparg >= 0;) { 
    w = POP(); 
    if (err == 0) 
     err = PySet_Add(x, w); 
    Py_DECREF(w); 
} 

natomiast kod bajtowy dodaje elementy na stosie w kolejności odwrotnej (popychanie 0 na stosie pierwszy):

>>> from dis import dis 
>>> dis(compile('{0, x, y}', '', 'eval')) 
    2   0 LOAD_CONST    1 (0) 
       3 LOAD_GLOBAL    0 (x) 
       6 LOAD_GLOBAL    1 (y) 
       9 BUILD_SET    3 
      12 RETURN_VALUE 

Poprawka polega na odczytaniu elementów ze stosu w odwrotnej kolejności; Python 2.7.13 version wykorzystuje PEEK() zamiast POP() (i STACKADJ() usunąć elementy ze stosu potem):

for (i = oparg; i > 0; i--) { 
    w = PEEK(i); 
    if (err == 0) 
     err = PySet_Add(x, w); 
    Py_DECREF(w); 
} 
STACKADJ(-oparg); 

testowania równości problem ma tę samą przyczynę jak inne pytanie; Klasa Decimal() ma problemy z równą wersją z complex tutaj, która została naprawiona w Pythonie 3.2 (przez utworzenie Decimal() support comparisons to complex and a few other numeric types it didn't support before).

+10

Czy uważasz, że to błąd, że równość nie jest przechodnia dla tych trzech typów? –

+7

@StevenRumbalski: Tak myślę, tak. Dlaczego '0j' nie powinno być równe' Decimal ('0') 'gdy' 0j == 0' jest prawdziwe i 'Decimal ('0') == 0' jest również prawdziwe. Python 3.2 naprawił to, aktualizując porównania "dziesiętne". –

+2

'bytearray ('ab') == 'ab' == u'ab'' również przełamuje przechodniość, ale ponieważ' bytearray' nie jest nieosiągalny, nie ma tam znaczenia. – wim

7

Wszystko sprowadza się do kolejności, w której zestaw został skonstruowany, w połączeniu z błędem, który wykryto za pomocą your other question. Wydaje się, że literał jest skonstruowany w odwrotnej kolejności do konwersji z listy.

>>> {0, x, y} 
set([0j, Decimal('0')]) 
>>> {y, x, 0} 
set([0]) 
+0

Ach, tak, to jest wnikliwe. Co powiesz na źródło kodu cpython, które pokazuje, w jaki sposób literał jest przetwarzany od prawej do lewej? – wim

+0

@wim: Nie mogłem znaleźć kodu, tylko dlatego, że błąd został ostatnio naprawiony. –

+0

@wim tak zabawne jak to brzmi, nigdy nie poszedłem spelunkować kodu źródłowego Python. –