Associativité de «in» en Python?

107

Je crée un analyseur Python, et cela me déroute vraiment :

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

Comment fonctionne exactement "in" en Python, en ce qui concerne l'associativité, etc.?

Pourquoi deux de ces expressions ne se comportent-elles pas de la même manière?

user541686
la source
6
Vous rencontrez probablement le comportement décrit ici: docs.python.org/reference/expressions.html#not-in , celui qui vous permet d'écrire if a < b < c:et de le faire fonctionner intuitivement
millimoose
3
@millimoose: Ouais, je n'ai jamais pensé inà un opérateur de "comparaison", je suppose. : \
user541686

Réponses:

123

1 in [] in 'a'est évalué comme (1 in []) and ([] in 'a').

Puisque la première condition ( 1 in []) est False, la condition entière est évaluée comme False; ([] in 'a')n'est jamais réellement évalué, donc aucune erreur n'est générée.

Voici les définitions des instructions:

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
la source
Whoa !! +1 C'est incroyable, merci beaucoup! Cela a l'air vraiment pratique, si seulement je le savais! Savez-vous où cela se trouve dans la documentation? J'ai cherché mais je n'ai rien trouvé qui suggérait cela!
user541686
1
note: []est faux, mais []ne l'est pas Falsepar exemple, [] and anythingretourne [](pas False).
jfs
6
@Mehrdad Découvrez le désassembleur Python qui a été utilisé avec iPython pour générer cette sortie.
Jeff Ferland
Je ne sais pas quelle version de Python a généré cela, mais Python 3.2 a apparemment un nouveau bytecode: JUMP_IF_FALSE_OR_POP, qui raccourcit la séquence d'une instruction de 13 à 12. Bonne réponse - merci !!
Dave
@Dave It's python 2.6.6 (iPython)
Ashwini Chaudhary
22

Python fait des choses spéciales avec des comparaisons chaînées.

Les éléments suivants sont évalués différemment:

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"

Dans les deux cas cependant, si la première comparaison est False, le reste de l'instruction ne sera pas examiné.

Pour votre cas particulier,

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'

Également pour illustrer la première règle ci-dessus, ce sont des instructions qui évaluent à 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

Préséance des opérateurs python: http://docs.python.org/reference/expressions.html#summary

Alexander Chen
la source
11

De la documentation:

Les comparaisons peuvent être chaînées arbitrairement, par exemple, x <y <= z équivaut à x <y et y <= z, sauf que y est évalué une seule fois (mais dans les deux cas z n'est pas évalué du tout lorsque x <y est trouvé être faux).

Ce que cela signifie, c'est qu'il n'y a pas d'associativité x in y in z!

Les éléments suivants sont équivalents:

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
la source
3

La réponse courte, puisque la longue est déjà donnée plusieurs fois ici et d'excellente manière, est que l'expression booléenne est court-circuitée , c'est a arrêté l'évaluation quand un changement de vrai en faux ou vice versa ne peut pas se produire par une évaluation plus poussée.

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

Cela peut être un peu court (sans jeu de mots) comme réponse, mais comme mentionné, toutes les autres explications sont déjà assez bien faites ici, mais je pensais que le terme méritait d'être mentionné.

Peter
la source