Por que `a == b ou c ou d` sempre avalia como True?

108

Estou escrevendo um sistema de segurança que nega o acesso a usuários não autorizados.

import sys

print("Hello. Please enter your name:")
name = sys.stdin.readline().strip()
if name == "Kevin" or "Jon" or "Inbar":
    print("Access granted.")
else:
    print("Access denied.")

Ele concede acesso a usuários autorizados conforme o esperado, mas também permite a entrada de usuários não autorizados!

Hello. Please enter your name:
Bob
Access granted.

Por que isso ocorre? Afirmei claramente que só concedo acesso quando for nameigual a Kevin, Jon ou Inbar. Também tentei a lógica oposta if "Kevin" or "Jon" or "Inbar" == name, mas o resultado é o mesmo.

Kevin
fonte
1
@Jean-François FYI houve alguma discussão sobre esta questão e seu alvo enganoso anteriormente na sala do python, a discussão começa aqui . Eu entendo se você deseja que ele seja fechado, mas achei que você poderia querer saber sobre os motivos pelos quais a postagem foi reaberta recentemente. Divulgação completa: Martijn, o autor da resposta sobre o alvo enganoso, ainda não teve tempo de se intrometer no assunto.
Andras Deak de
A resposta de Martijn é excelente, explicando-a com "não use linguagem natural", outros, bem, ... aqueles foram tempos de votação gloriosos ... A resposta abaixo apenas repete isso. Para mim, é uma duplicata. Mas se Martijn decidir reabrir, bem, não me importo.
Jean-François Fabre
4
Variações deste problema incluem x or y in z, x and y in z, x != y and ze alguns outros. Embora não seja exatamente idêntica a esta pergunta, a causa raiz é a mesma para todas elas. Só queria salientar isso, caso alguém tenha fechado sua pergunta como uma duplicata desta e não tenha certeza de como isso é relevante para eles.
Aran-Fey

Respostas:

152

Em muitos casos, Python se parece e se comporta como o inglês natural, mas este é um caso em que essa abstração falha. As pessoas podem usar pistas de contexto para determinar que "Jon" e "Inbar" são objetos unidos ao verbo "igual a", mas o interpretador Python é mais literal.

if name == "Kevin" or "Jon" or "Inbar":

é logicamente equivalente a:

if (name == "Kevin") or ("Jon") or ("Inbar"):

O que, para o usuário Bob, é equivalente a:

if (False) or ("Jon") or ("Inbar"):

O oroperador escolhe o primeiro argumento com um valor verdade positivo :

if ("Jon"):

E como "Jon" tem um valor verdadeiro positivo, o ifbloco é executado. Isso é o que faz com que "Acesso concedido" seja impresso independentemente do nome fornecido.

Todo esse raciocínio também se aplica à expressão if "Kevin" or "Jon" or "Inbar" == name. o primeiro valor "Kevin",, é verdadeiro, então o ifbloco é executado.


Existem duas maneiras comuns de construir adequadamente esta condicional.

  1. Use vários ==operadores para verificar explicitamente cada valor:
    if name == "Kevin" or name == "Jon" or name == "Inbar":

  2. Componha uma sequência de valores válidos e use o inoperador para testar a associação:
    if name in {"Kevin", "Jon", "Inbar"}:

Em geral dos dois, o segundo deve ser o preferido, pois é mais fácil de ler e também mais rápido:

>>> import timeit
>>> timeit.timeit('name == "Kevin" or name == "Jon" or name == "Inbar"', setup="name='Inbar'")
0.4247764749999945
>>> timeit.timeit('name in {"Kevin", "Jon", "Inbar"}', setup="name='Inbar'")
0.18493307199999265

Para aqueles que podem querer uma prova de que if a == b or c or d or e: ...é realmente analisado assim. O astmódulo integrado fornece uma resposta:

>>> import ast
>>> ast.parse("if a == b or c or d or e: ...")
<_ast.Module object at 0x1031ae6a0>
>>> ast.dump(_)
"Module(body=[If(test=BoolOp(op=Or(), values=[Compare(left=Name(id='a', ctx=Load()), ops=[Eq()], comparators=[Name(id='b', ctx=Load())]), Name(id='c', ctx=Load()), Name(id='d', ctx=Load()), Name(id='e', ctx=Load())]), body=[Expr(value=Ellipsis())], orelse=[])])"
>>>

Portanto, o testda ifdeclaração é assim:

BoolOp(
 op=Or(),
 values=[
  Compare(
   left=Name(id='a', ctx=Load()),
   ops=[Eq()],
   comparators=[Name(id='b', ctx=Load())]
  ),
  Name(id='c', ctx=Load()),
  Name(id='d', ctx=Load()),
  Name(id='e', ctx=Load())
 ]
)

Como se pode ver, é o operador booleano oraplicada a múltiplos values, ou seja, a == be c, de e.

Kevin
fonte
Existe um motivo específico para escolher uma tupla em ("Kevin", "Jon", "Inbar")vez de um conjunto {"Kevin", "Jon", "Inbar"} ?
Humano
2
Na verdade não, já que ambos funcionam se os valores forem todos hashable. O teste de associação de conjunto tem melhor complexidade big-O do que o teste de associação de tupla, mas construir um conjunto é um pouco mais caro do que construir uma tupla. Acho que é basicamente uma lavagem para pequenas coleções como essas. Brincar com o timeit a in {b, c, d}é duas vezes mais rápido do que a in (b, c, d)na minha máquina. Algo para se pensar se este é um código de desempenho crítico.
Kevin
3
Tupla ou lista ao usar 'in' em uma cláusula 'if'? recomenda definir literais para teste de associação. Vou atualizar meu post.
Kevin
No Python moderno, ele reconhece que o conjunto é uma constante e, em frozensetvez disso, o torna uma , de modo que a sobrecarga do conjunto de construção não está lá. dis.dis(compile("1 in {1, 2, 3}", '<stdin>', 'eval'))
endolito de
1

Problema de engenharia simples, vamos um pouco mais longe.

In [1]: a,b,c,d=1,2,3,4
In [2]: a==b
Out[2]: False

Mas, herdado da linguagem C, Python avalia o valor lógico de um inteiro diferente de zero como True.

In [11]: if 3:
    ...:     print ("yey")
    ...:
yey

Agora, Python se baseia nessa lógica e permite que você use literais lógicos, como ou em inteiros, e assim

In [9]: False or 3
Out[9]: 3

Finalmente

In [4]: a==b or c or d
Out[4]: 3

A maneira correta de escrever seria:

In [13]: if a in (b,c,d):
    ...:     print('Access granted')

Por segurança, eu também sugiro que você não codifique senhas.

user1854182
fonte
1

Existem 3 verificações de condições if name == "Kevin" or "Jon" or "Inbar":

  • nome == "Kevin"
  • "Jon"
  • "No bar"

e esta declaração if é equivalente a

if name == "Kevin":
    print("Access granted.")
elif "Jon":
    print("Access granted.")
elif "Inbar":
    print("Access granted.")
else:
    print("Access denied.")

Visto elif "Jon"que sempre será verdadeiro, o acesso a qualquer usuário é concedido

Solução


Você pode usar qualquer um dos métodos abaixo

Rápido

if name in ["Kevin", "Jon", "Inbar"]:
    print("Access granted.")
else:
    print("Access denied.")

Lento

if name == "Kevin" or name == "Jon" or name == "Inbar":
    print("Access granted.")
else:
    print("Access denied.")

Código lento + desnecessário

if name == "Kevin":
    print("Access granted.")
elif name == "Jon":
    print("Access granted.")
elif name == "Inbar":
    print("Access granted.")
else:
    print("Access denied.")
7u5h4r
fonte