【问题标题】:Why does "a == x or y or z" always evaluate to True?为什么“a == x or y or z”总是评估为 True?
【发布时间】:2022-01-22 12:54:11
【问题描述】:

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

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,但结果是一样的。

注意:这个问题旨在作为这个非常常见问题的规范重复目标。还有另一个热门问题How to test multiple variables against a single value? 有同样的基本问题,但比较的目标是相反的。这个问题不应该作为那个问题的副本来结束,因为这个问题是 Python 新手遇到的,他们可能难以将反向问题的知识应用于他们的问题。

【问题讨论】:

  • 这个问题的变种包括x or y in zx and y in zx != y and z 和其他一些。虽然与这个问题并不完全相同,但根本原因对所有人来说都是相同的。只是想指出这一点,以防有人将他们的问题作为重复而关闭,并且不确定它与他们有什么关系。

标签: python boolean boolean-expression


【解决方案1】:

在许多情况下,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 运算符选择第一个带有正值truth value 的参数:

if "Jon":

由于“Jon”的真值是正的,所以 if 块执行。这就是导致无论给出的名称如何都会打印“授予访问权限”的原因。

所有这些推理也适用于表达式if "Kevin" or "Jon" or "Inbar" == name。第一个值"Kevin" 为真,因此执行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

【讨论】:

  • 选择一个元组("Kevin", "Jon", "Inbar")而不是一个集合{"Kevin", "Jon", "Inbar"}是否有特定的原因?
  • 并非如此,因为如果值都是可散列的,则两者都有效。集合成员资格测试比元组成员资格测试具有更好的大 O 复杂性,但是构建一个集合比构建一个元组要贵一点。我认为这在很大程度上是对像这样的小型收藏品的洗礼。在我的机器上使用 timeit,a in {b, c, d} 的速度大约是 a in (b, c, d) 的两倍。如果这是一段对性能至关重要的代码,需要考虑一下。
  • Tuple or list when using 'in' in an 'if' clause? 建议使用集合文字进行成员资格测试。我会更新我的帖子。
  • 在现代 Python 中,它认识到集合是一个常量,并将其设为 frozenset,因此不存在构造集合的开销。 dis.dis(compile("1 in {1, 2, 3}", '&lt;stdin&gt;', 'eval'))
【解决方案2】:

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

  • 姓名 == “凯文”
  • “乔恩”
  • “英巴”

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

【讨论】:

    【解决方案3】:

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

    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 建立在该逻辑之上,并允许您在整数上使用逻辑文字,例如 or 等​​

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

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

    【讨论】:

      【解决方案4】:

      非空列表、集合、字符串等是可评估的,因此返回 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
      

      【讨论】:

      • 否则很好,但“你实际上是在说:”是错误,这不是or 的工作方式。表达式的值为"John",而不是True
      【解决方案5】:

      方法

      数据科学家如何解决这个问题

      最简单的方法是消除比较运算符的需要并使用列表。这在安全系统上看起来令人印象深刻,因为您学会了访问 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.")
      

      如果您想安全地完成此协议而没有受到攻击的风险,请设置双重参数。这将检查您的 mini-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。

      【讨论】:

        猜你喜欢
        • 2022-02-25
        • 2022-01-09
        相关资源
        最近更新 更多