Dlaczego wyrażenie 0 <0 == 0 zwraca False w Pythonie?

136

Patrząc na Queue.py w Pythonie 2.6, znalazłem tę konstrukcję, która wydała mi się nieco dziwna:

def full(self):
    """Return True if the queue is full, False otherwise
    (not reliable!)."""
    self.mutex.acquire()
    n = 0 < self.maxsize == self._qsize()
    self.mutex.release()
    return n

Jeśli maxsizejest 0, kolejka nigdy nie jest pełna.

Moje pytanie brzmi, jak to działa w tym przypadku? Jak 0 < 0 == 0jest uważane za fałsz?

>>> 0 < 0 == 0
False
>>> (0) < (0 == 0)
True
>>> (0 < 0) == 0
True
>>> 0 < (0 == 0)
True
Marcelo Santos
źródło
Czy 0 <True równa się False w Pythonie?
Marino Šimić
3
@Marino Šimić: Z drugiego przykładu przedstawionego w pytaniu PO >>> (0) < (0 == 0)wyraźnie nie jest.
martineau
3
Jednym z powodów, dla których nie powinieneś pisać kodu tak jak n = 0 < self.maxsize == self._qsize()w pierwszej kolejności, w żadnym języku. Jeśli twoje oczy muszą kilka razy przesuwać się w tę iz powrotem po linii, aby dowiedzieć się, co się dzieje, nie jest to dobrze napisana linia. Po prostu podziel go na kilka wierszy.
BlueRaja - Danny Pflughoeft
2
@Blue: Zgadzam się, że nie piszę takiego porównania w ten sposób, ale podzielenie go na oddzielne wiersze to trochę przesada w przypadku dwóch porównań. Mam nadzieję, że masz na myśli, podziel to na oddzielne porównania. ;)
Jeff Mercado
2
@Blue: Nie napisałem tego, jest w Pythonie 2.6. Próbowałem tylko zrozumieć, co się dzieje.
Marcelo Santos

Odpowiedzi:

113

Uważam, że Python obsługuje specjalne przypadki w sekwencjach operatorów relacyjnych, aby porównania zakresów były łatwe do wyrażenia. O wiele przyjemniej jest móc powiedzieć 0 < x <= 5niż powiedzieć (0 < x) and (x <= 5).

Nazywa się to porównaniami łańcuchowymi . A to jest link do dokumentacji dla nich.

W innych przypadkach, o których mówisz, nawiasy wymuszają zastosowanie jednego operatora relacji przed drugim, więc nie są one już połączonymi porównaniami. A ponieważ Truei Falsemasz wartości jako liczby całkowite, otrzymujesz odpowiedzi z wersji ujętych w nawiasy.

Wszelaki
źródło
warto wypróbować niektóre z tych porównań i określić int () i bool (). Zdałem sobie sprawę, że bool () dowolnej wartości niezerowej wynosi 1. Chyba nigdy nie próbowałbym bezpośrednio określić niczego innego niż bool (0) lub bool (1) przed tym eksperymentem
myślowym
Czy zamiast tego zamierzałeś zamieścić link do tej sekcji? docs.python.org/2/reference/expressions.html#comparisons
tavnab
@tavnab - Tak. Postaram się pamiętać, żeby to naprawić. Sprawdzę też historię zmian. To nie wygląda na błąd, który bym popełnił. 😕
Omnifarious
42

Ponieważ

(0 < 0) and (0 == 0)

jest False. Operatory porównania można łączyć w łańcuchy, które są automatycznie rozszerzane do porównań parami.


EDYCJA - wyjaśnienie dotyczące prawdy i fałszu w Pythonie

W Pythonie Truei Falsesą tylko instancjami bool, które są podklasą int. Innymi słowy, Truetak naprawdę to tylko 1.

Chodzi o to, że wyniku porównania logicznego można używać dokładnie tak, jak liczby całkowitej. Prowadzi to do mylących rzeczy, takich jak

>>> (1==1)+(1==1)
2
>>> (2<1)<1
True

Ale to się stanie tylko wtedy, gdy umieścisz porównania w nawiasach, tak aby były oceniane jako pierwsze. W przeciwnym razie Python rozszerzy operatory porównania.

Katriel
źródło
2
Widziałem wczoraj interesujące użycie wartości boolowskich jako liczb całkowitych. Wyrażenie 'success' if result_code == 0 else 'failure'można przepisać, ponieważ ('error', 'success')[result_code == 0]przedtem nigdy nie widziałem wartości logicznej używanej do wybierania pozycji z listy / krotki.
Andrew Clark
„bool” zostało dodane gdzieś w okolicach Pythona 2.2.
MRAB
18

Dziwne zachowanie, którego doświadczasz, wynika ze zdolności Pythona do łączenia warunków. Ponieważ stwierdza, że ​​0 nie jest mniejsze niż 0, decyduje o tym, że całe wyrażenie ma wartość fałsz. Gdy tylko podzielisz to na osobne warunki, zmieniasz funkcjonalność. Początkowo zasadniczo testuje to a < b && b == cdla pierwotnego oświadczenia o a < b == c.

Inny przykład:

>>> 1 < 5 < 3
False

>>> (1 < 5) < 3
True
Tyler
źródło
1
OMG, a < b && b == cto to samo co a < b == cOO
Kiril Kirov
9
>>> 0 < 0 == 0
False

To porównanie łańcuchowe. Zwraca prawdę, jeśli po kolei każde porównanie parami jest prawdziwe. To jest odpowiednik(0 < 0) and (0 == 0)

>>> (0) < (0 == 0)
True

Jest to równoważne z 0 < Truewartością True.

>>> (0 < 0) == 0
True

Jest to równoważne z False == 0wartością True.

>>> 0 < (0 == 0)
True

Odpowiednik, do 0 < Truektórego, jak powyżej, przyjmuje wartość True.

David Heffernan
źródło
7

Patrząc na demontaż (kody bajtów) widać, dlaczego tak 0 < 0 == 0jest False.

Oto analiza tego wyrażenia:

>>>import dis

>>>def f():
...    0 < 0 == 0

>>>dis.dis(f)
  2      0 LOAD_CONST               1 (0)
         3 LOAD_CONST               1 (0)
         6 DUP_TOP
         7 ROT_THREE
         8 COMPARE_OP               0 (<)
        11 JUMP_IF_FALSE_OR_POP    23
        14 LOAD_CONST               1 (0)
        17 COMPARE_OP               2 (==)
        20 JUMP_FORWARD             2 (to 25)
   >>   23 ROT_TWO
        24 POP_TOP
   >>   25 POP_TOP
        26 LOAD_CONST               0 (None)
        29 RETURN_VALUE

Zwróć uwagę na linie 0-8: te wiersze sprawdzają, czy 0 < 0który oczywiście wraca Falsena stos Pythona.

Zwróćcie teraz uwagę na wiersz 11: JUMP_IF_FALSE_OR_POP 23 Oznacza to, że jeśli 0 < 0zwraca, Falsewykonuje skok do wiersza 23.

Teraz 0 < 0jest False, więc wykonywany jest skok, który pozostawia stos z Falsewartością zwracaną dla całego wyrażenia 0 < 0 == 0, nawet jeśli == 0część nie jest nawet zaznaczona.

Podsumowując, odpowiedź jest taka, jak w innych odpowiedziach na to pytanie. 0 < 0 == 0ma specjalne znaczenie. Kompilator ocenia to na dwa warunki: 0 < 0i 0 == 0. Podobnie jak w przypadku każdego złożonego wyrażenia logicznego z andmiędzy nimi, jeśli pierwsze zawiedzie, to drugie nie jest nawet sprawdzane.

Mam nadzieję, że to trochę rozjaśnia sprawę i mam nadzieję, że metoda, której użyłem do analizy tego nieoczekiwanego zachowania, zachęci innych do spróbowania tego samego w przyszłości.

SatA
źródło
Czy nie byłoby łatwiej wypracować to na podstawie specyfikacji zamiast inżynierii wstecznej jednej konkretnej implementacji?
David Heffernan
Nie. To krótka odpowiedź. Uważam, że to zależy od Twojej osobowości. Jeśli odnosisz się do widoku „czarnej skrzynki” i wolisz uzyskiwać odpowiedzi na podstawie specyfikacji i dokumentacji, ta odpowiedź tylko Cię zmyli. Jeśli lubisz zagłębiać się i odkrywać wnętrze rzeczy, ta odpowiedź jest dla Ciebie. Twoja uwaga, że ​​ta inżynieria odwrotna dotyczy tylko jednej konkretnej implementacji, jest poprawna i należy ją wskazać, ale nie o to chodzi w tej odpowiedzi. Jest to demonstracja, jak łatwo w Pythonie zajrzeć „pod maskę” tym, którzy są wystarczająco ciekawi.
SatA,
1
Problem z inżynierią odwrotną polega na braku mocy predykcyjnej. To nie jest sposób na naukę nowego języka.
David Heffernan,
Kolejną rzeczą, którą muszę dodać, jest to, że specyfikacje i dokumentacje nie zawsze są kompletne iw większości przypadków nie dostarczają odpowiedzi na tak konkretne przypadki. Wtedy, jak sądzę, nie można bać się eksploracji, badania i sięgania głębiej, tak bardzo, jak jest to potrzebne, aby uzyskać odpowiedzi.
SatA,
2

Jak wspomniano u innych, x comparison_operator y comparison_operator zjest to cukier syntaktyczny (x comparison_operator y) and (y comparison_operator z)z premią, że y jest oceniane tylko raz.

Więc twoje wyrażenie 0 < 0 == 0jest naprawdę (0 < 0) and (0 == 0), które ocenia, False and Truektóre jest sprawiedliwe False.

dr jimbob
źródło
2

może ten fragment dokumentacji może pomóc:

Są to tak zwane metody „bogatego porównania” i są wywoływane jako operatory porównania, a nie __cmp__()poniżej. Odpowiedniość między symbolami operatora i nazwy, sposób jest następujący: x<ypołączeń x.__lt__(y), x<=ypołączenia x.__le__(y), x==ypołączenia x.__eq__(y), x!=yi x<>y połączeń x.__ne__(y), x>ypołączenia x.__gt__(y), i x>=ypołączenia x.__ge__(y).

Bogata metoda porównania może zwrócić singleton, NotImplementedjeśli nie implementuje operacji dla danej pary argumentów. Zgodnie z konwencją Falsei Truesą zwracane w celu pomyślnego porównania. Jednak metody te mogą zwracać dowolną wartość, więc jeśli operator porównania jest używany w kontekście boolowskim (np. W warunku instrukcji if), Python wywoła bool()wartość, aby określić, czy wynik jest prawdziwy, czy fałszywy.

Nie ma domniemanych relacji między operatorami porównania. Prawda x==ynie oznacza, że x!=y jest fałszywa. W związku z tym przy definiowaniu __eq__()należy również zdefiniować __ne__()tak, aby operatorzy zachowywali się zgodnie z oczekiwaniami. Zapoznaj się z paragrafem, __hash__()aby uzyskać kilka ważnych uwag na temat tworzenia obiektów haszujących, które obsługują niestandardowe operacje porównania i są używane jako klucze słownika.

Nie ma wersji tych metod z zamienionymi argumentami (do użycia, gdy lewy argument nie obsługuje operacji, ale prawy argument tak); raczej, __lt__()i __gt__() są odbiciem siebie nawzajem, __le__() i __ge__()to odbicie siebie, a __eq__()i __ne__() są ich własnym odbiciem.

Argumenty dotyczące bogatych metod porównawczych nigdy nie są wymuszane.

To były porównania, ale ponieważ łączysz porównania łańcuchowe , powinieneś wiedzieć, że:

Porównania można łączyć dowolnie, np. Są x < y <= zrównoważne x < y and y <= z, z wyjątkiem tego, że y jest oceniane tylko raz (ale w obu przypadkach z nie jest w ogóle oceniane, gdy x <y okaże się fałszywe).

Formalnie, jeśli a, b, c, ..., y, z są wyrażeniami, a op1, op2, ..., opN są operatorami porównania, to a op1 b op2 c ... y opN z jest równoważne a op1 b i b op2 c i ... y opN z, z tą różnicą, że każde wyrażenie jest oceniane najwyżej raz.

Marino Šimić
źródło
1

Oto ona, w całej okazałości.

>>> class showme(object):
...   def __init__(self, name, value):
...     self.name, self.value = name, value
...   def __repr__(self):
...     return "<showme %s:%s>" % (self.name, self.value)
...   def __cmp__(self, other):
...     print "cmp(%r, %r)" % (self, other)
...     if type(other) == showme:
...       return cmp(self.value, other.value)
...     else:
...       return cmp(self.value, other)
... 
>>> showme(1,0) < showme(2,0) == showme(3,0)
cmp(<showme 1:0>, <showme 2:0>)
False
>>> (showme(1,0) < showme(2,0)) == showme(3,0)
cmp(<showme 1:0>, <showme 2:0>)
cmp(<showme 3:0>, False)
True
>>> showme(1,0) < (showme(2,0) == showme(3,0))
cmp(<showme 2:0>, <showme 3:0>)
cmp(<showme 1:0>, True)
True
>>> 
SingleNegationElimination
źródło
0

Myślę, że Python robi to dziwnie między magią. To samo, co 1 < 2 < 3oznacza, że ​​2 jest między 1 a 3.

W tym przypadku myślę, że wykonanie [środkowe 0] jest większe niż [lewe 0] i równe [prawe 0]. Środkowe 0 nie jest większe niż lewe 0, więc przyjmuje wartość fałsz.

mpen
źródło