¿Por qué "a == x o y o z" siempre se evalúa como Verdadero? ¿Cómo puedo comparar "a" con todos esos?
Estoy escribiendo un sistema de seguridad que niega el acceso a usuarios no autorizados.
name = input("Hello. Please enter your name: ")
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 a usuarios no autorizados!
Hello. Please enter your name: Bob
Access granted.
¿Por qué ocurre esto? He dicho claramente que solo se otorgará acceso cuando name
sea igual a Kevin, Jon o Inbar. También probé la lógica opuesta, if "Kevin" or "Jon" or "Inbar" == name
pero el resultado es el mismo.
Esta pregunta pretende ser el objetivo duplicado canónico de este problema tan común. Hay otra pregunta popular: ¿Cómo probar la igualdad de múltiples variables con un solo valor? eso tiene el mismo problema fundamental, pero los objetivos de comparación están invertidos. Esta pregunta no debe cerrarse como un duplicado de aquella, ya que los recién llegados a Python encuentran este problema y pueden tener dificultades para aplicar el conocimiento de la pregunta invertida a su problema.
En in
lugar de ==
, hay soluciones aquí: Cómo probar la pertenencia de múltiples valores en una lista
En muchos casos, Python se ve y se comporta como el inglés natural, pero este es un caso en el que esa abstracción falla. Las personas pueden usar pistas de contexto para determinar que "Jon" e "Inbar" son objetos unidos al verbo "equals", 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 cual, para el usuario Bob, equivale a:
if (False) or ("Jon") or ("Inbar"):
El or
operador elige el primer operando que es "verdadero" , es decir, el que cumpliría una if
condición (o el último, si ninguno de ellos es "verdadero"):
if "Jon":
Como "Jon" es veraz, el if
bloque 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 if
se ejecuta el bloque.
Hay tres formas comunes de construir correctamente este condicional.
Utilice varios
==
operadores para comprobar explícitamente cada valor:if name == "Kevin" or name == "Jon" or name == "Inbar":
Cree una colección de valores válidos (un conjunto, una lista o una tupla, por ejemplo) y utilice el
in
operador para probar la membresía:if name in {"Kevin", "Jon", "Inbar"}:
Utilice
any()
y una expresión generadora para comparar explícitamente cada valor en un bucle:if any(name == auth for auth in ["Kevin", "Jon", "Inbar"]):
En general, se debería preferir 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.0960568820592016
>>> timeit.timeit('name in {"Kevin", "Jon", "Inbar"}', setup="name='Inbar'")
0.034957461059093475
>>> timeit.timeit('any(name == auth for auth in ["Kevin", "Jon", "Inbar"])',
setup="name='Inbar'")
0.6511583919636905
Para aquellos que quieran pruebas de que if a == b or c or d or e: ...
realmente se analizan así. El módulo incorporado ast
proporciona una respuesta:
>>> import ast
>>> ast.parse("a == b or c or d or e", "<string>", "eval")
<ast.Expression object at 0x7f929c898220>
>>> print(ast.dump(_, indent=4))
Expression(
body=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 or
aplicado a cuatro subexpresiones: comparación a == b
; y expresiones simples c
, d
, y e
.
Resumiendo todas las respuestas existentes
(Y agregando algunos de mis puntos)
Explicación :
if name == "Kevin" or "Jon" or "Inbar":
es lógicamente equivalente a:
if (name == "Kevin") or ("Jon") or ("Inbar"):
Lo cual, para el usuario Bob, equivale a:
if (False) or ("Jon") or ("Inbar"):
NOTA: Python evalúa el valor lógico de cualquier número entero distinto de cero como True
. Por lo tanto, todas las listas, conjuntos, cadenas, etc. no vacíos son evaluables y devuelvenTrue
El or
operador elige el primer argumento con un valor de verdad positivo.
Por lo tanto, "Jon" tiene un valor de verdad positivo y el bloque if se ejecuta, ya que ahora es equivalente a
if (False) or (True) or (True):
Eso es lo que hace que se imprima "Acceso concedido" independientemente del nombre introducido.
Soluciones:
Solución 1: utilice varios ==
operadores para comparar explícitamente cada valor
if name == "Kevin" or name == "Jon" or name == "Inbar":
print("Access granted.")
else:
print("Access denied.")
Solución 2: cree una colección de valores válidos (un conjunto, una lista o una tupla, por ejemplo) y utilice el in
operador para probar la membresía (método preferido, más rápido)
if name in {"Kevin", "Jon", "Inbar"}:
print("Access granted.")
else:
print("Access denied.")
O
if name in ["Kevin", "Jon", "Inbar"]:
print("Access granted.")
else:
print("Access denied.")
Solución 3: utilice la estructura básica (y no muy eficiente) if-elif-else
if name == "Kevin":
print("Access granted.")
elif name == "Jon":
print("Access granted.")
elif name == "Inbar":
print("Access granted.")
else:
print("Access denied.")
Hay 3 controles de condición enif name == "Kevin" or "Jon" or "Inbar":
- nombre == "Kevin"
- "Jon"
- "En el bar"
y esta declaración si es equivalente a
if name == "Kevin":
print("Access granted.")
elif "Jon":
print("Access granted.")
elif "Inbar":
print("Access granted.")
else:
print("Access denied.")
Puesto que elif "Jon"
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.")
Las listas, conjuntos, cadenas, etc. no vacíos son evaluables y, por lo tanto, devuelven Verdadero.
Por eso, cuando dices:
a = "Raul"
if a == "Kevin" or "John" or "Inbar":
pass
En realidad estás diciendo:
if "Raul" == "Kevin" or "John" != "" or "Inbar" != "":
pass
Dado que al menos uno de "John" e "Inbar" no es una cadena vacía, la expresión completa siempre devuelve Verdadero.
La solución:
a = "Raul"
if a == "Kevin" or a == "John" or a == "Inbar":
pass
o:
a = "Raul"
if a in {"Kevin", "John", "Inbar"}:
pass
Problema de ingeniería simple, vamos a simplificarlo un poco más.
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 número 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 en números enteros, etc.
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 le sugiero que no codifique contraseñas.
Usando match
/ case
en Python 3.10 y superior
Python 3.10 agrega una nueva sintaxis al lenguaje. Se describe oficialmente como "coincidencia de patrones estructurales", pero la mayoría de la gente lo llama según la sintaxis: " match
/ case
".
- Especificación técnica
- Motivación y justificación (es decir, por qué se agregó y qué inspiró el diseño)
- tutorial oficial
Podemos usar esta sintaxis especial para un ejemplo como en la pregunta, creando un "caso" que coincida con todos los nombres de usuario aceptados y usando el caso "comodín" _
en lugar del else
. De este modo:
name = input("Hello. Please enter your name: ")
match name:
case "Kevin" | "Jon" | "Inbar":
print("Access granted.")
case _:
print("Access denied.")
Tenga en cuenta que los casos se "combinan" usando |
, no or
. Esta es una sintaxis especial: Python no intenta calcular "Kevin" | "Jon" | "Inbar"
primero ( |
no funciona con cadenas), sino que interpreta la línea completa de manera diferente porque comienza con case
.