为什么“ a == b或c或d”总是评估为True?

问题:为什么“ a == b或c或d”总是评估为True?

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

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.")

它可以按预期授予授权用户访问权限,但也允许未经授权的用户使用!

Hello. Please enter your name:
Bob
Access granted.

为什么会发生这种情况?我已经明确指出仅在name等于Kevin,Jon或Inbar 时才授予访问权限。我也尝试过相反的逻辑if "Kevin" or "Jon" or "Inbar" == name,但是结果是一样的。

I am writing a security system that denies access to unauthorized users.

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.")

It grants access to authorized users as expected, but it also lets in unauthorized users!

Hello. Please enter your name:
Bob
Access granted.

Why does this occur? I’ve plainly stated to only grant access when name equals Kevin, Jon, or Inbar. I have also tried the opposite logic, if "Kevin" or "Jon" or "Inbar" == name, but the result is the same.


回答 0

在许多情况下,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"):

并且由于“ Jon”具有正的真值,因此if执行该块。这就是导致无论给定名称如何都将打印“授予访问权限”的原因。

所有这些推理也适用于表达式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("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=[])])"
>>>

因此test,该if语句的如下所示:

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应用于多个values,即a == bcde

In many cases, Python looks and behaves like natural English, but this is one case where that abstraction fails. People can use context clues to determine that “Jon” and “Inbar” are objects joined to the verb “equals”, but the Python interpreter is more literal minded.

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

is logically equivalent to:

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

Which, for user Bob, is equivalent to:

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

The or operator chooses the first argument with a positive truth value:

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.

All of this reasoning also applies to the expression if "Kevin" or "Jon" or "Inbar" == name. the first value, "Kevin", is true, so the if block executes.


There are two common ways to properly construct this conditional.

  1. Use multiple == operators to explicitly check against each value:
    if name == "Kevin" or name == "Jon" or name == "Inbar":

  2. Compose a sequence of valid values, and use the in operator to test for membership:
    if name in {"Kevin", "Jon", "Inbar"}:

In general of the two the second should be preferred as it’s easier to read and also faster:

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

For those who may want proof that if a == b or c or d or e: ... is indeed parsed like this. The built-in ast module provides an answer:

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

So the test of the if statement looks like this:

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

As one can see, it’s the boolean operator or applied to multiple values, namely, a == b and c, d, and e.


回答 1

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

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

但是,Python继承自语言C,因此将非零整数的逻辑值评估为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')

为了安全起见,我还建议您不要对密码进行硬编码。

Simple engineering problem, let’s simply it a bit further.

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

But, inherited from the language C, Python evaluates the logical value of a non zero integer as True.

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

Now, Python builds on that logic and let you use logic literals such as or on integers, and so

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

Finally

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

The proper way to write it would be:

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

For safety I’d also suggest you don’t hard code passwords.


回答 2

有3个条件检查 if name == "Kevin" or "Jon" or "Inbar":

  • 名称==“凯文”
  • “乔恩”
  • “ Inbar”

这个if语句等效于

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

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


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

快速

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.")

There are 3 condition checks in if name == "Kevin" or "Jon" or "Inbar":

  • name == “Kevin”
  • “Jon”
  • “Inbar”

and this if statement is equivalent to

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

Since elif "Jon" will always be true so access to any user is granted

Solution


You can use any one method below

Fast

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

Slow

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

Slow + Unnecessary code

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