Łączność „w” w Pythonie?

107

Robię parser Pythona i to mnie naprawdę dezorientuje:

>>>  1 in  []  in 'a'
False

>>> (1 in  []) in 'a'
TypeError: 'in <string>' requires string as left operand, not bool

>>>  1 in ([] in 'a')
TypeError: 'in <string>' requires string as left operand, not list

Jak dokładnie działa „in” w Pythonie, w odniesieniu do asocjatywności itp.?

Dlaczego żadne dwa z tych wyrażeń nie zachowują się tak samo?

user541686
źródło
6
Prawdopodobnie uderzasz w zachowanie opisane tutaj: docs.python.org/reference/expressions.html#not-in , to, które pozwala pisać if a < b < c:i działać intuicyjnie
millimoose
3
@millimoose: Tak, po prostu nigdy nie myślałem o tym injako o operatorze „porównania”. : \
user541686

Odpowiedzi:

123

1 in [] in 'a' jest oceniany jako (1 in []) and ([] in 'a') .

Ponieważ pierwszym warunkiem ( 1 in []) jest False, cały warunek jest oceniany jako False; ([] in 'a')nie jest nigdy obliczana, więc nie jest zgłaszany żaden błąd.

Oto definicje instrukcji:

In [121]: def func():
   .....:     return 1 in [] in 'a'
   .....: 

In [122]: dis.dis(func)
  2           0 LOAD_CONST               1 (1)
              3 BUILD_LIST               0
              6 DUP_TOP             
              7 ROT_THREE           
              8 COMPARE_OP               6 (in)
             11 JUMP_IF_FALSE            8 (to 22)  #if first comparison is wrong 
                                                    #then jump to 22, 
             14 POP_TOP             
             15 LOAD_CONST               2 ('a')
             18 COMPARE_OP               6 (in)     #this is never executed, so no Error
             21 RETURN_VALUE         
        >>   22 ROT_TWO             
             23 POP_TOP             
             24 RETURN_VALUE        

In [150]: def func1():
   .....:     return (1 in  []) in 'a'
   .....: 

In [151]: dis.dis(func1)
  2           0 LOAD_CONST               1 (1)
              3 LOAD_CONST               3 (())
              6 COMPARE_OP               6 (in)   # perform 1 in []
              9 LOAD_CONST               2 ('a')  # now load 'a'
             12 COMPARE_OP               6 (in)   # compare result of (1 in []) with 'a'
                                                  # throws Error coz (False in 'a') is
                                                  # TypeError
             15 RETURN_VALUE   



In [153]: def func2():
   .....:     return 1 in ([] in 'a')
   .....: 

In [154]: dis.dis(func2)
  2           0 LOAD_CONST               1 (1)
              3 BUILD_LIST               0
              6 LOAD_CONST               2 ('a') 
              9 COMPARE_OP               6 (in)  # perform ([] in 'a'), which is 
                                                 # Incorrect, so it throws TypeError
             12 COMPARE_OP               6 (in)  # if no Error then 
                                                 # compare 1 with the result of ([] in 'a')
             15 RETURN_VALUE        
Ashwini Chaudhary
źródło
Whoa !! +1 To niesamowite, wielkie dzięki! Wygląda naprawdę poręcznie, gdybym tylko o tym wiedział! Czy wiesz, gdzie to jest w dokumentacji? I wyglądało , ale nie mógł znaleźć niczego, co zaproponował ten!
user541686
1
uwaga: []jest fałszem, ale np. []nie zwraca (nie ). False[] and anything[]False
jfs
6
@Mehrdad Zapoznaj się z deasemblerem języka Python, który był używany z iPythonem do wygenerowania tego wyniku.
Jeff Ferland
Nie wiem, która wersja Pythona to wygenerowała, ale Python 3.2 najwyraźniej ma nowy kod bajtowy: JUMP_IF_FALSE_OR_POP, który skraca sekwencję o jedną instrukcję z 13 do 12. Fajna odpowiedź - dzięki !!
Dave,
@Dave It's python 2.6.6 (iPython)
Ashwini Chaudhary
22

Python robi specjalne rzeczy za pomocą łańcuchowych porównań.

Następujące elementy są oceniane w różny sposób:

x > y > z   # in this case, if x > y evaluates to true, then
            # the value of y is being used to compare, again,
            # to z

(x > y) > z # the parenth form, on the other hand, will first
            # evaluate x > y. And, compare the evaluated result
            # with z, which can be "True > z" or "False > z"

Jednak w obu przypadkach, jeśli pierwsze porównanie jest False , reszta stwierdzenia nie zostanie rozpatrzona.

W Twoim konkretnym przypadku

1 in [] in 'a'   # this is false because 1 is not in []

(1 in []) in a   # this gives an error because we are
                 # essentially doing this: False in 'a'

1 in ([] in 'a') # this fails because you cannot do
                 # [] in 'a'

Aby zademonstrować pierwszą zasadę powyżej, są to stwierdzenia, których wynikiem jest prawda.

1 in [1,2] in [4,[1,2]] # But "1 in [4,[1,2]]" is False

2 < 4 > 1               # and note "2 < 1" is also not true

Pierwszeństwo operatorów Pythona: http://docs.python.org/reference/expressions.html#summary

Alexander Chen
źródło
11

Z dokumentacji:

Porównania można łączyć dowolnie, np. X <y <= z jest równoważne x <y i y <= z, z wyjątkiem tego, że y jest oceniane tylko raz (ale w obu przypadkach z nie jest w ogóle oceniane, gdy zostanie znalezione x <y być fałszywe).

Oznacza to, że nie ma w nim skojarzeń x in y in z!

Poniższe są równoważne:

1 in  []  in 'a'
# <=>
middle = []
#            False          not evaluated
result = (1 in middle) and (middle in 'a')


(1 in  []) in 'a'
# <=>
lhs = (1 in []) # False
result = lhs in 'a' # False in 'a' - TypeError


1 in  ([] in 'a')
# <=>
rhs = ([] in 'a') # TypeError
result = 1 in rhs
phant0m
źródło
3

Krótka odpowiedź, ponieważ długa jest już tu podawana kilka razy i to w doskonały sposób, jest taka, że ​​wyrażenie boolowskie jest zwarte , to jest zatrzymanie oceny, gdy zmiana prawdy na fałsz lub odwrotnie nie może nastąpić przez dalszą ocenę.

(patrz http://en.wikipedia.org/wiki/Short-circuit_evaluation )

Odpowiedź może być trochę krótka (gra słów nie jest zamierzona), ale jak wspomniano, wszystkie inne wyjaśnienia są już tutaj całkiem dobrze zrobione, ale pomyślałem, że termin zasługuje na wspomnienie.

Piotr
źródło