¿Asociatividad de "en" en Python?

107

Estoy haciendo un analizador de Python, y esto me confunde mucho:

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

¿Cómo funciona exactamente "in" en Python, con respecto a la asociatividad, etc.?

¿Por qué no hay dos de estas expresiones que se comporten de la misma manera?

usuario541686
fuente
6
Probablemente esté alcanzando el comportamiento que se describe aquí: docs.python.org/reference/expressions.html#not-in , el que le permite escribir if a < b < c:y hacer que funcione de manera intuitiva
millimoose
3
@millimoose: Sí, supongo que nunca pensé inen un operador de "comparación". : \
user541686

Respuestas:

123

1 in [] in 'a'se evalúa como (1 in []) and ([] in 'a').

Dado que la primera condición ( 1 in []) es False, toda la condición se evalúa como False; ([] in 'a')nunca se evalúa realmente, por lo que no se genera ningún error.

Aquí están las definiciones de las declaraciones:

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
fuente
Whoa !! +1 ¡Eso es increíble, muchas gracias! ¡Parece realmente útil, si tan solo lo supiera! ¿Sabe dónde está esto en la documentación? ¡ Miré pero no pude encontrar nada que sugiriera esto!
user541686
1
nota: []es falso, pero []no lo es False, por ejemplo, [] and anythingdevuelve [](no False).
jfs
6
@Mehrdad Consulte el desensamblador de Python que se utilizó con iPython para generar esta salida.
Jeff Ferland
No sé qué versión de Python generó esto, pero Python 3.2 aparentemente tiene un nuevo código de bytes: JUMP_IF_FALSE_OR_POP, que acorta la secuencia en una instrucción de 13 a 12. ¡Buena respuesta, gracias!
Dave
@Dave It's Python 2.6.6 (iPython)
Ashwini Chaudhary
22

Python hace cosas especiales con comparaciones encadenadas.

Los siguientes se evalúan de manera diferente:

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"

Sin embargo, en ambos casos, si la primera comparación es False, no se examinará el resto de la declaración.

Para tu caso particular,

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'

También para demostrar la primera regla anterior, estas son declaraciones que se evalúan como Verdadero.

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

Prioridad de los operadores de Python: http://docs.python.org/reference/expressions.html#summary

Alexander Chen
fuente
11

De la documentación:

Las comparaciones se pueden encadenar arbitrariamente, por ejemplo, x <y <= z es equivalente ax <y e y <= z, excepto que y se evalúa solo una vez (pero en ambos casos z no se evalúa en absoluto cuando se encuentra x <y ser falso).

Lo que esto significa es que no hay asociatividad en x in y in z!

Los siguientes son equivalentes:

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

La respuesta corta, dado que la larga ya se da varias veces aquí y de manera excelente, es que la expresión booleana está en cortocircuito , esto se ha detenido la evaluación cuando un cambio de verdadero en falso o viceversa no puede ocurrir mediante una evaluación adicional.

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

Puede ser un poco corto (sin juego de palabras) como respuesta, pero como se mencionó, todas las demás explicaciones ya están bastante bien hechas aquí, pero pensé que el término merecía ser mencionado.

Pedro
fuente