Dlaczego w Pythonie „0, 0 == (0, 0)” równa się „(0, False)”?

118

W Pythonie (sprawdzałem tylko z Pythonem 3.6, ale uważam, że powinno to wytrzymać również wiele poprzednich wersji):

(0, 0) == 0, 0   # results in a two element tuple: (False, 0)
0, 0 == (0, 0)   # results in a two element tuple: (0, False)
(0, 0) == (0, 0) # results in a boolean True

Ale:

a = 0, 0
b = (0, 0)
a == b # results in a boolean True

Dlaczego wynik różni się w obu podejściach? Czy operator równości obsługuje krotki inaczej?

Piotr Zakrzewski
źródło

Odpowiedzi:

156

Pierwsze dwa wyrażenia analizują jako krotki:

  1. (0, 0) == 0(czyli False), po którym następuje0
  2. 0, po 0 == (0, 0)którym następuje (co nadal jest w Falseten sposób).

Wyrażenia są podzielone w ten sposób ze względu na względne pierwszeństwo separatora przecinków w porównaniu z operatorem równości: Python widzi krotkę zawierającą dwa wyrażenia, z których jedno jest testem równości zamiast testu równości między dwiema krotkami.

Ale w drugim zestawie instrukcji a = 0, 0 nie może być krotką. Krotka to zbiór wartości iw przeciwieństwie do testu równości przypisanie nie ma wartości w Pythonie. Przypisanie nie jest wyrażeniem, ale instrukcją; nie ma wartości, którą można uwzględnić w krotce lub jakimkolwiek innym otaczającym ją wyrażeniu. Jeśli spróbujesz czegoś takiego (a = 0), 0, jak wymuszenie interpretacji jako krotki, otrzymasz błąd składniowy. To pozostawia przypisanie krotki do zmiennej - którą można by uściślić, pisząc ją a = (0, 0)- jako jedyną prawidłową interpretację a = 0, 0.

Więc nawet bez nawiasów w przypisaniu do a, zarówno to, jak i botrzymają przypisaną wartość (0,0), więc tak a == bjest True.

Mark Reed
źródło
17
Powiedziałbym, że operator przecinka ma niższy priorytet niż równość, ponieważ ocena równości poprzedza ocenę operatora przecinka: równość ma wyższy priorytet niż operator przecinka. Ale to zawsze jest źródłem nieporozumień; chciałem tylko zwrócić uwagę, że inne źródła mogą odwrócić sytuację.
tomsmeding
2
Możesz uniknąć pomieszania z niższą / wyższą słownictwem, mówiąc zamiast tego, że ,wiąże się mniej ściśle niż ==.
amalloy
4
Przecinek nie jest operatorem docs.python.org/3.4/faq/…
Chris_Rands
48
Doktorzy mogą twierdzić, że chcą, ale to nie ma znaczenia. Możesz napisać parser tak, aby każdy operator miał swoją własną produkcję i nie ma wyraźnego "pierwszeństwa" nigdzie w implementacji, ale to nie powstrzymuje tych jednostek syntaktycznych przed byciem operatorami. Możesz przedefiniować "operator" w sposób specyficzny dla implementacji , co najwyraźniej jest tym, co zrobili w Pythonie, ale to nie zmienia implikacji tego terminu. Przecinek jest skutecznie operatora, który wytwarza krotki. Jego operatorowość pokazuje na przykład sposób, w jaki nawiasy wpływają na jego względne pierwszeństwo.
Mark Reed
68

To, co widzisz we wszystkich trzech przypadkach, jest konsekwencją specyfikacji gramatycznej języka i sposobu, w jaki tokeny napotkane w kodzie źródłowym są analizowane w celu wygenerowania drzewa parsowania.

Spojrzenie na ten kod niskiego poziomu powinno pomóc ci zrozumieć, co dzieje się pod maską. Możemy wziąć te instrukcje Pythona, przekonwertować je na kod bajtowy, a następnie zdekompilować za pomocą dismodułu:

Przypadek 1: (0, 0) == 0, 0

>>> dis.dis(compile("(0, 0) == 0, 0", '', 'exec'))
  1           0 LOAD_CONST               2 ((0, 0))
              3 LOAD_CONST               0 (0)
              6 COMPARE_OP               2 (==)
              9 LOAD_CONST               0 (0)
             12 BUILD_TUPLE              2
             15 POP_TOP
             16 LOAD_CONST               1 (None)
             19 RETURN_VALUE

(0, 0)jest najpierw porównywany z 0pierwszym i oceniany do False. Następnie tworzona jest krotka z tym wynikiem i ostatnim 0, więc otrzymujesz (False, 0).

Przypadek 2: 0, 0 == (0, 0)

>>> dis.dis(compile("0, 0 == (0, 0)", '', 'exec'))
  1           0 LOAD_CONST               0 (0)
              3 LOAD_CONST               0 (0)
              6 LOAD_CONST               2 ((0, 0))
              9 COMPARE_OP               2 (==)
             12 BUILD_TUPLE              2
             15 POP_TOP
             16 LOAD_CONST               1 (None)
             19 RETURN_VALUE

Krotka jest konstruowana z 0pierwszym elementem. W przypadku drugiego elementu wykonywane jest to samo sprawdzenie, co w pierwszym przypadku i oceniane False, więc otrzymujesz (0, False).

Przypadek 3: (0, 0) == (0, 0)

>>> dis.dis(compile("(0, 0) == (0, 0)", '', 'exec'))
  1           0 LOAD_CONST               2 ((0, 0))
              3 LOAD_CONST               3 ((0, 0))
              6 COMPARE_OP               2 (==)
              9 POP_TOP
             10 LOAD_CONST               1 (None)
             13 RETURN_VALUE

Tutaj, jak widzisz, po prostu porównujesz te dwie (0, 0)krotki i zwracasz True.

cs95
źródło
20

Inny sposób wyjaśnienia problemu: prawdopodobnie znasz dosłowne słownikowe

{ "a": 1, "b": 2, "c": 3 }

i literały tablicowe

[ "a", "b", "c" ]

i literały krotki

( 1, 2, 3 )

ale nie zdajesz sobie sprawy z tego, że w przeciwieństwie do literałów słownikowych i tablicowych, nawiasy, które zwykle widzisz wokół literału krotki, nieczęścią składni literału . Dosłowna składnia krotek to po prostu sekwencja wyrażeń oddzielonych przecinkami:

1, 2, 3

(„lista wyrażeń” w języku gramatyki formalnej dla Pythona ).

Czego oczekujesz od literału tablicowego

[ 0, 0 == (0, 0) ]

oceniać? To prawdopodobnie wygląda bardziej tak, jak powinno być takie samo jak

[ 0, (0 == (0, 0)) ]

który oczywiście jest oceniany do [0, False]. Podobnie z jawnie umieszczonym w nawiasach literałem krotki

( 0, 0 == (0, 0) )

nie jest to zaskakujące (0, False). Ale nawiasy są opcjonalne;

0, 0 == (0, 0)

to to samo. I dlatego otrzymujesz (0, False).


Jeśli zastanawiasz się, dlaczego nawiasy wokół literału krotki są opcjonalne, dzieje się tak głównie dlatego, że byłoby to denerwujące, gdyby trzeba było pisać zadania niszczące w ten sposób:

(a, b) = (c, d) # meh
a, b = c, d     # better
zwol
źródło
17

Dodanie kilku nawiasów wokół kolejności wykonywania czynności może pomóc w lepszym zrozumieniu wyników:

# Build two element tuple comprising of 
# (0, 0) == 0 result and 0
>>> ((0, 0) == 0), 0
(False, 0)

# Build two element tuple comprising of
# 0 and result of (0, 0) == 0 
>>> 0, (0 == (0, 0))
(0, False)

# Create two tuples with elements (0, 0) 
# and compare them
>>> (0, 0) == (0, 0) 
True

Przecinek służy do oddzielania wyrażeń (używając nawiasów możemy oczywiście wymusić inne zachowanie). Podczas przeglądania wymienionych fragmentów przecinek ,oddzieli je i określi, które wyrażenia zostaną ocenione:

(0, 0) == 0 ,   0
#-----------|------
  expr 1      expr2

Krotkę (0, 0)można również podzielić w podobny sposób. Przecinek oddziela dwa wyrażenia składające się z literałów 0.

Dimitris Fasarakis Hilliard
źródło
6

W pierwszym Python tworzy krotkę dwóch rzeczy:

  1. Wyrażenie (0, 0) == 0, którego wynikiem jestFalse
  2. Stała 0

W drugim jest odwrotnie.

kindall
źródło
0

spójrz na ten przykład:

r = [1,0,1,0,1,1,0,0,0,1]
print(r==0,0,r,1,0)
print(r==r,0,1,0,1,0)

wynik:

False 0 [1, 0, 1, 0, 1, 1, 0, 0, 0, 1] 1 0
True 0 1 0 1 0

następnie porównanie działa tylko z pierwszą liczbą (0 i r) w przykładzie.

Emad Saeidi
źródło