¿Por qué `a == bo c o d` siempre se evalúa como Verdadero?

108

Estoy escribiendo un sistema de seguridad que niega el acceso a usuarios no 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.")

Otorga acceso a usuarios autorizados como se esperaba, ¡pero también permite la entrada de usuarios no autorizados!

Hello. Please enter your name:
Bob
Access granted.

¿Por qué ocurre esto? He dicho claramente que solo otorgaré acceso cuando sea nameigual a Kevin, Jon o Inbar. También he probado la lógica opuesta if "Kevin" or "Jon" or "Inbar" == name, pero el resultado es el mismo.

Kevin
fuente
1
@ Jean-François FYI hubo una discusión sobre esta pregunta y su objetivo engañoso anteriormente en la sala de Python, la discusión comienza aquí . Entiendo si desea que se cierre, pero pensé que es posible que desee conocer las razones por las que la publicación se reabrió recientemente. Revelación completa: Martijn, el autor de la respuesta sobre el objetivo engañado aún no ha tenido tiempo de intervenir en el asunto.
Andras Deak
La respuesta de Martijn es excelente al explicarlo con "no uses lenguaje natural", otros, bueno, ... esos fueron tiempos gloriosos de votaciones a favor ... La respuesta a continuación simplemente repite esto. Para mí es un duplicado. Pero si Martijn decide reabrir, bueno, no me importa.
Jean-François Fabre
4
Las variaciones de este problema son x or y in z, x and y in z, x != y and zy algunos otros. Si bien no es exactamente idéntico a esta pregunta, la causa raíz es la misma para todos ellos. Solo quería señalar eso en caso de que alguien cerrara su pregunta como un duplicado de esto y no estuviera seguro de cómo es relevante para ellos.
Aran-Fey

Respuestas:

152

En muchos casos, Python se ve y se comporta como el inglés natural, pero este es un caso en el que falla esa abstracción. La gente puede usar pistas de contexto para determinar que "Jon" e "Inbar" son objetos unidos al verbo "igual", pero el intérprete de Python tiene una mentalidad más literal.

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

es lógicamente equivalente a:

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

Lo que, para el usuario Bob, equivale a:

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

El oroperador elige el primer argumento con un valor de verdad positivo :

if ("Jon"):

Y dado que "Jon" tiene un valor de verdad positivo, el ifbloque se ejecuta. Eso es lo que hace que se imprima "Acceso concedido" independientemente del nombre dado.

Todo este razonamiento también se aplica a la expresión if "Kevin" or "Jon" or "Inbar" == name. el primer valor "Kevin",, es verdadero, por lo que el ifbloque se ejecuta.


Hay dos formas comunes de construir correctamente este condicional.

  1. Utilice varios ==operadores para comprobar explícitamente cada valor:
    if name == "Kevin" or name == "Jon" or name == "Inbar":

  2. Componga una secuencia de valores válidos y use el inoperador para probar la pertenencia:
    if name in {"Kevin", "Jon", "Inbar"}:

En general, de los dos, debería preferirse el segundo, ya que es más fácil de leer y también más 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 aquellos que quieran una prueba de que de if a == b or c or d or e: ...hecho se analiza así. El astmódulo integrado proporciona una respuesta:

>>> 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=[])])"
>>>

Entonces, el testde la ifdeclaración se ve así:

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 puede ver, es el operador booleano oraplica a múltiples values, a saber, a == by c, d, y e.

Kevin
fuente
¿Existe una razón específica para elegir una tupla en ("Kevin", "Jon", "Inbar")lugar de un conjunto {"Kevin", "Jon", "Inbar"} ?
Human
2
Realmente no, ya que ambos funcionan si todos los valores son hash. Las pruebas de pertenencia a conjuntos tienen una mayor complejidad de big-O que las pruebas de pertenencia a tuplas, pero construir un conjunto es un poco más caro que construir una tupla. Creo que es en gran parte un lavado para pequeñas colecciones como estas. Jugar con timeit a in {b, c, d}es aproximadamente el doble de rápido que a in (b, c, d)en mi máquina. Algo en lo que pensar si se trata de una pieza de código crítica para el rendimiento.
Kevin
3
¿Tupla o lista cuando se usa 'in' en una cláusula 'if'? recomienda establecer literales para las pruebas de membresía. Actualizaré mi publicación.
Kevin
En Python moderno, reconoce que el conjunto es una constante y lo convierte en una frozenset, por lo que la sobrecarga del conjunto de construcción no está allí. dis.dis(compile("1 in {1, 2, 3}", '<stdin>', 'eval'))
endolito
1

Problema de ingeniería simple, simplemente vayamos un poco más lejos.

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

Pero, heredado del lenguaje C, Python evalúa el valor lógico de un entero distinto de cero como Verdadero.

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

Ahora, Python se basa en esa lógica y le permite usar literales lógicos como o sobre enteros, y así

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

Finalmente

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

La forma correcta de escribirlo sería:

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

Por seguridad, también te sugiero que no codifiques las contraseñas.

usuario1854182
fuente
1

Hay 3 comprobaciones de estado en if name == "Kevin" or "Jon" or "Inbar":

  • nombre == "Kevin"
  • "Jon"
  • "En el bar"

y esta declaración if es equivalente a

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

Dado elif "Jon"que siempre será cierto por lo que se concede acceso a cualquier usuario

Solución


Puede utilizar cualquiera de los métodos siguientes

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

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