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

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.


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.


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

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.


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.


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.


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.


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

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.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.