2

I'm trying to learn Python, and I've stumbled across this incredibly strange anomality whilst programming a "quiz" for my own learning purposes. The boolean "or" function is acting a little weird. Here's a piece of the code:

def answercheck(answer):
    solved = 0
    while solved == 0:
        useranswer = raw_input("> ")
        if useranswer == answer:
            solved = 1
            correct()
        elif useranswer == "A" or "B" or "C" or "D":
            solved = 1
            incorrect()
        else:
            print "False input. Try again."

Here's what happens. The answercheck function is called with the real answer as argument after a quiz question is printed out for the quiz contestant. The user is then prompted for an answer. Because I want the only valid answers to be "A", "B", "C" or "D", I've put the thing in a "while" loop.

So, I would suspect, if someone made 'useranswer' something useless like 'lol' through raw_input, it would prompt the user again until "A", "B", "C" or "D" is inserted as answer. It doesn't, however, and takes any answer that isn't 'answer' as incorrect.

So I rewrote the thing a little, and it appears to work when I do this:

def answercheck(answer):
    solved = 0
    while solved == 0:
        useranswer = raw_input("> ")
        if useranswer == answer:
            solved = 1
            correct()
        elif useranswer == "A":
            solved = 1
            incorrect()
        elif useranswer == "B":
            solved = 1
            incorrect()
        elif useranswer == "C":
            solved = 1
            incorrect()
        elif useranswer == "D":
            solved = 1
            incorrect()
        else:
            print "False input. Try again."

This seems a little redundant to me. What is going wrong in the first script?

Ruben Bakker
  • 446
  • 1
  • 5
  • 11
  • `or` is an expression, not a function.. – Martijn Pieters Mar 05 '13 at 17:31
  • That isn't how `or` works - `or` takes the left hand side and the right hand side, and returns `True` if either side evaluates to `True`. What you have written makes sense in English, but not Python. – Gareth Latty Mar 05 '13 at 17:31
  • 1
    _or_ is not a traditional boolean operator. It evaluates to the first item where bool(item)==True. So _'fred' or 'jane'_ is 'fred' and _None or 'jane'_ is 'jane'. With variables, _x=None; y='jane'; print x or y_ prints 'jane'. – tdelaney Mar 05 '13 at 17:48
  • NEVER assume that the language itself has a bug. 99.999999% of the time, the problem is within your own code. You're not using it correctly, thus it doesn't yield what you expects it would yield. – Rushy Panchal Mar 05 '13 at 18:04

4 Answers4

4

The correct syntax for useranswer is any of "A", "B", "C" or "D" is:

elif useranswer in ("A", "B", "C", "D"):

or (if your version of Python is recent enough to support set literals):

elif useranswer in {"A", "B", "C", "D"}:

What you have now is syntactically valid, but has an entirely different meaning.

NPE
  • 486,780
  • 108
  • 951
  • 1,012
  • 1
    Or, optimally, use a set: `useranswer in {"A", "B", "C", "D"}:`. This will perform slightly better since checking for membership on a set is `O(1)` as opposed to `O(n)` for a tuple. This won't tend to matter much in most situations, but it's a good habit to get into. – Gareth Latty Mar 05 '13 at 17:32
  • 1
    @Lattyware -- except in this particular case where a set takes twice a s long as tuple. Try `timeit.timeit('useranswer in ("A", "B", "C", "D")', 'useranswer="D"',number=10000000)/10000000` vs `timeit.timeit('useranswer in {"A", "B", "C", "D"}', 'useranswer="D"',number=10000000)/10000000` vs `timeit.timeit('useranswer in "ABCD"', 'useranswer="D"',number=10000000)/10000000`. `O(1)` does not go faster than `O(n)` for every possible input. It goes faster for every sufficiently large input. – Robᵩ Mar 05 '13 at 17:40
  • @Robᵩ Indeed, I did say it was unlikely to matter in this case - the difference is going to be imperceptible either way. In general, though, I tend to try and use sets for this kind of thing in case it later becomes some massive check where it does matter. – Gareth Latty Mar 05 '13 at 17:49
3

or operates on two boolean variables; it does not create a set of alternative choices. Try this:

useranswer == "A" or useranswer == "B" or useranswer == "C" or useranswer == "D"

Or, more conveniently :

useranswer in ("A", "B", "C", "D")

Or even

useranswer in "ABCD"
Robᵩ
  • 163,533
  • 20
  • 239
  • 308
1

useranswer == "A" or "B" or "C" or "D" means

(((useranswer == "A") or "B") or "C") or "D"

That's why it does not work as you like

gefei
  • 18,922
  • 9
  • 50
  • 67
1

Operator Precedence - It evaluates useranswer == 'A' to whatever, and then "B", "C", "D" always evaluate to "True"

You need to do something like this"

if useranswer in ['A', 'B', 'C', 'D']:
    ...
karthikr
  • 97,368
  • 26
  • 197
  • 188