为什么“ a = = x 或 y 或 z”总是求值为 True?我怎么能把“ A”和这些比较呢?

我正在编写一个安全系统,拒绝访问未经授权的用户。

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

它按预期授予授权用户访问权,但也允许未授权用户进入!

Hello. Please enter your name: Bob
Access granted.

为什么会这样?我已经明确表示,只有当 name等于 Kevin、 Jon 或 Inbar 时,才授予访问权限。我也尝试了相反的逻辑,if "Kevin" or "Jon" or "Inbar" == name,但结果是相同的。


这个问题旨在作为这个非常常见问题的规范重复目标。还有一个流行的问题 如何测试多个变量对单个值的相等性?也有同样的根本问题,但是比较目标是相反的。这个问题不应该作为那个问题的副本而结束,因为这个问题是 Python 的新手遇到的,他们可能难以将反问题中的知识应用到他们的问题中。

38639 次浏览

在许多情况下,Python 看起来和行为都像自然英语,但是在这种情况下,抽象失败了。人们可以使用上下文提示来确定“ Jon”和“ Inbar”是与动词“ equals”联接的对象,但 Python 解释器更注重字面意义。

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

在逻辑上等同于:

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

对于用户 Bob,这相当于:

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

or操作符选择 真相价值为正的第一个参数:

if "Jon":

And since "Jon" has a positive truth value, the if block executes. That is what causes "Access granted" to be printed regardless of the name given.

所有这些推理也适用于表达式 if "Kevin" or "Jon" or "Inbar" == name。第一个值 "Kevin"为 true,因此执行 if块。


有两种正确构造此条件的常用方法。

  1. 使用多个 ==运算符显式检查每个值:

    if name == "Kevin" or name == "Jon" or name == "Inbar":
    
  2. 组成一个有效值的集合(例如一个集合、一个列表或一个元组) ,并使用 in运算符来测试成员资格:

    if name in {"Kevin", "Jon", "Inbar"}:
    

一般来说,第二种更好,因为它更容易阅读,也更快:

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

对于那些想要证明 if a == b or c or d or e: ...确实是这样解析的人来说,内置的 ast模块提供了一个答案:

>>> 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())]))

可以看到,它是应用于四个子表达式的布尔运算符 or: 比较 a == b; 以及简单表达式 cde

简单的工程问题,让我们再简单一点。

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

但是,继承自 C 语言的 Python 将非零整数的逻辑值计算为 True。

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

现在,Python 构建在这个逻辑之上,并且允许您使用逻辑文本,例如或整数,等等

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

终于来了

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

正确的写法应该是:

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

为了安全起见,我还建议你不要硬编码密码。

if name == "Kevin" or "Jon" or "Inbar":中有3个状态检查

  • Name = = “ Kevin”
  • 乔恩
  • “ Inbar”

这个 if 语句等价于

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

由于 elif "Jon"始终为 true,因此授予对任何用户的访问权

解决方案


您可以使用下面的任何一种方法

快点

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

慢点

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

慢速 + 不必要的代码

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

Not-empty lists, sets, strings, etc. are evaluable and, therefore, return True.

因此,当你说:

a = "Raul"
if a == "Kevin" or "John" or "Inbar":
pass

你实际上是在说:

if "Raul" == "Kevin" or "John" != "" or "Inbar" != "":
pass

因为“ John”和“ Inbar”中至少有一个不是空字符串,所以整个表达式总是返回 True!

解决办法:

a = "Raul"
if a == "Kevin" or a == "John" or a == "Inbar":
pass

或:

a = "Raul"
if a in {"Kevin", "John", "Inbar"}:
pass

接近

数据科学家如何处理这个问题

最简单的方法是消除对比较运算符的需要并使用列表。这在安全系统上看起来令人印象深刻,因为您学习了访问 ORM。

user = input("Enter name: ")


if user in {"Bob", "Kevin", "Joe"}:
print("Access granted, " + str(user) + ".")
else:
print("Access denied.")

或者,你也可以像上面的 一模一样代码一样,只要把注册用户的列表放在他们自己的列表中:

user = input("Enter name: ")
users = {"Bob", "Kevin", "Joe", "a million more users if you like"}


if user in users:
print("Access granted, " + str(user) + ".")
else:
print("Access denied.")

如果您希望安全地完成此协议而不受攻击的风险,请设置双参数。这将检查您的迷你 ORM 的 firstlast名字字段,以及 passwordsecret question键。如果您希望有效地延迟加载用户凭据而不使用散列,那么可以像下面这样对对象进行排序:

def lazy(i):
j = 0 # For example
while j < i:
yield j
j += 1

循环将消耗 只有产生的值,以节省系统上的时间和能源:

然后,您可以对迭代列表执行以下操作:

for j in lazy_range(10):
do_something_here(j)

这个问题可以从任何角度来解决: 内存管理、安全性,或者简单地通过一个有机列表或打包的 ORM 来解决。

总结所有现有的答案

(加上我的一些观点)

说明:

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

在逻辑上等同于:

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

对于用户 Bob,这相当于:

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

注意: Python 将任何非零整数的逻辑值计算为 True。因此,所有非空列表、集合、字符串等都是可计算的,并返回 True

or运算符选择第一个具有正真值的参数。

因此,“ Jon”具有一个正的 true 值,并且执行 if 块,因为它现在等效于

if (False) or (True) or (True):

这就是为什么不管输入的名称是什么,都会打印“已授予访问权限”。

解决方案:

解决方案1: 使用多个 ==操作符显式检查每个值

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

解决方案2: 组成一个有效值的集合(例如一个集合、一个列表或一个元组) ,并使用 in操作符来测试成员资格 < em > (更快,首选的方法)

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

或者

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

解决方案3: 使用基本的 (而且效率不高) 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.")