Associatività di "in" in Python?


107

Sto creando un parser Python e questo mi confonde davvero :

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

Come funziona esattamente "in" in Python, per quanto riguarda l'associatività, ecc.?

Perché nessuna di queste due espressioni si comporta allo stesso modo?


6
Probabilmente stai seguendo il comportamento descritto qui: docs.python.org/reference/expressions.html#not-in , quello che ti permette di scrivere if a < b < c:e farlo funzionare in modo intuitivo
millimoose

3
@millimoose: Sì, non ho mai pensato ina un operatore di "confronto" immagino. : \
user541686

Risposte:


123

1 in [] in 'a'viene valutato come (1 in []) and ([] in 'a').

Poiché la prima condizione ( 1 in []) è False, l'intera condizione viene valutata come False; ([] in 'a')non viene mai effettivamente valutato, quindi non viene generato alcun errore.

Ecco le definizioni delle dichiarazioni:

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        

Whoa !! +1 È fantastico, grazie mille! Sembra davvero utile, se solo lo sapessi! Sai dove si trova nella documentazione? Ho cercato ma non sono riuscito a trovare nulla che suggerisse questo!
user541686

1
nota: []è falso, ma []non lo è False, ad esempio, [] and anythingrestituisce [](non False).
jfs

6
@Mehrdad Controlla il disassemblatore di Python che è stato utilizzato con iPython per generare questo output.
Jeff Ferland,

Non so quale versione di Python l'ha generata, ma Python 3.2 apparentemente ha un nuovo bytecode: JUMP_IF_FALSE_OR_POP, che accorcia la sequenza di un'istruzione da 13 a 12. Bella risposta - grazie !!
Dave

@Dave It's python 2.6.6 (iPython)
Ashwini Chaudhary

22

Python fa cose speciali con confronti concatenati.

I seguenti vengono valutati in modo diverso:

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"

In entrambi i casi, tuttavia, se il primo confronto è False, il resto dell'istruzione non verrà esaminato.

Per il tuo caso particolare,

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'

Anche per dimostrare la prima regola sopra, queste sono dichiarazioni che restituiscono True.

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

Precedenza degli operatori python: http://docs.python.org/reference/expressions.html#summary


11

Dalla documentazione:

I confronti possono essere concatenati arbitrariamente, ad esempio, x <y <= zèequivalente a x <y e y <= z, tranne per il fatto che y viene valutato solo una volta (ma in entrambi i casi z non viene valutato affatto quando si trova x <y essere falso).

Ciò significa che non c'è associatività in x in y in z!

I seguenti sono equivalenti:

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

3

La risposta breve, poiché quella lunga è già data più volte qui e in modi eccellenti, è che l'espressione booleana è in cortocircuito , questa ha interrotto la valutazione quando un cambio di vero in falso o viceversa non può avvenire con un'ulteriore valutazione.

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

Potrebbe essere un po 'breve (nessun gioco di parole) come risposta, ma come detto, tutte le altre spiegazioni sono già fatte abbastanza bene qui, ma ho pensato che il termine meritasse di essere menzionato.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.