Associatividade de “in” em Python?

107

Estou fazendo um analisador Python, e isso está realmente me confundindo:

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

Como exatamente "in" funciona em Python, em relação à associatividade, etc.?

Por que duas dessas expressões não se comportam da mesma maneira?

user541686
fonte
6
Você provavelmente está atingindo o comportamento descrito aqui: docs.python.org/reference/expressions.html#not-in , aquele que permite que você escreva if a < b < c:e trabalhe intuitivamente
milimoose
3
@millimoose: Sim, nunca pensei que fosse inum operador de "comparação", acho. : \
user541686

Respostas:

123

1 in [] in 'a'é avaliado como (1 in []) and ([] in 'a').

Como a primeira condição ( 1 in []) é False, toda a condição é avaliada como False; ([] in 'a')nunca é realmente avaliado, portanto, nenhum erro é gerado.

Aqui estão as definições das declarações:

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
fonte
Uau!! +1 Isso é incrível, muito obrigado! Parece muito útil, se eu soubesse disso! Você sabe onde isso está na documentação? Eu olhei , mas não consegui encontrar nada que sugerisse isso!
user541686
1
nota: []é falso, mas []não é False, por exemplo, [] and anythingretorna [](não False).
jfs
6
@Mehrdad Verifique o desmontador Python que foi usado com iPython para gerar esta saída.
Jeff Ferland
Não sei qual versão do Python gerou isso, mas o Python 3.2 aparentemente tem um novo bytecode: JUMP_IF_FALSE_OR_POP, que encurta a sequência em uma instrução de 13 para 12. Resposta legal - obrigado !!
Dave
@Dave It's python 2.6.6 (iPython)
Ashwini Chaudhary
22

Python faz coisas especiais com comparações encadeadas.

Os itens a seguir são avaliados de forma 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"

Em ambos os casos, se a primeira comparação for False, o resto da instrução não será analisado.

Para o seu 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'

Também para demonstrar a primeira regra acima, essas são declarações avaliadas como 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

Precedência de operadores Python: http://docs.python.org/reference/expressions.html#summary

Alexander Chen
fonte
11

Da documentação:

As comparações podem ser encadeadas arbitrariamente, por exemplo, x <y <= z é equivalente a x <y e y <= z, exceto que y é avaliado apenas uma vez (mas em ambos os casos z não é avaliado quando x <y é encontrado para ser falso).

O que isso significa é que não há associatividade em x in y in z!

Os seguintes são 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
fonte
3

A resposta curta, uma vez que a longa já é dada várias vezes aqui e de maneiras excelentes, é que a expressão booleana está em curto-circuito , isto é interrompido a avaliação quando uma mudança de verdadeiro para falso ou vice-versa não pode acontecer por avaliação posterior.

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

Pode ser um pouco curto (sem trocadilhos) como resposta, mas como mencionei, todas as outras explicações já foram feitas muito bem aqui, mas achei que o termo merecia ser mencionado.

Peter
fonte