Search code examples
pythonbooleanboolean-expression

Why does "a == x or y or z" always evaluate to True? How can I compare "a" to all of those?


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

name = input("Hello. Please enter your name: ")
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.


This question is intended as the canonical duplicate target of this very common problem. There is another popular question How to test multiple variables for equality against a single value? that has the same fundamental problem, but the comparison targets are reversed. This question should not be closed as a duplicate of that one as this problem is encountered by newcomers to Python who might have difficulties applying the knowledge from the reversed question to their problem.

For in instead of ==, there are solutions here: How to test the membership of multiple values in a list


Solution

  • 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 operand that is "truthy", i.e. which would satisfy an if condition (or the last one, if none of them are "truthy"):

    if "Jon":
    

    Since "Jon" is truthy, 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 three 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 collection of valid values (a set, a list or a tuple for example), and use the in operator to test for membership:

      if name in {"Kevin", "Jon", "Inbar"}:
      
    3. Use any() and a generator expression to explicitly check against each value in a loop:

      if any(name == auth for auth in ["Kevin", "Jon", "Inbar"]):
      

    In general 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.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
    

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

    As one can see, it's the boolean operator or applied to four sub-expressions: comparison a == b; and simple expressions c, d, and e.