19

Is it possible to ask for selection between multiple choices in Python, without an if loop?

Example:

print "Do you want to enter the door"
raw_input ("Yes or not")

And the user can only choose between the selections.

Community
  • 1
  • 1
Gianni Alessandro
  • 860
  • 6
  • 11
  • 28
  • 1
    `if` is not a loop. Why don't you want to use `if`? –  Jun 01 '16 at 10:17
  • the idea is that if you don't write "Yes" or "No", nothing will appear, the program will just wait from you to write one of the two and not proceeding. – Gianni Alessandro Jun 01 '16 at 10:20
  • You are going to need a loop to do that – Keatinge Jun 01 '16 at 10:23
  • You can't really avoid a loop and an if for this case (I mean it's certainly possible without but why someone would do that ?). If the problem is that you don't want a lot of loops and if in your code just write a function. – polku Jun 01 '16 at 10:24

6 Answers6

43

If you need to do this on a regular basis, there is a convenient library for this purpose that may help you achieve better user experience easily : inquirer

Disclaimer : As far as i know, it won't work on Windows without some hacks.

You can install inquirer with pip :

pip install inquirer

Example 1 : Multiple choices

One of inquirer's feature is to let users select from a list with the keyboard arrows keys, not requiring them to write their answers. This way you can achieve better UX for your console application.

Here is an example taken from the documentation :

import inquirer
questions = [
  inquirer.List('size',
                message="What size do you need?",
                choices=['Jumbo', 'Large', 'Standard', 'Medium', 'Small', 'Micro'],
            ),
]
answers = inquirer.prompt(questions)
print answers["size"]

Inquirer example

Example 2 : Yes/No questions :

For "Yes/No" questions such as yours, you can even use inquirer's Confirm :

import inquirer
confirm = {
    inquirer.Confirm('confirmed',
                     message="Do you want to enter the door ?" ,
                     default=True),
}
confirmation = inquirer.prompt(confirm)
print confirmation["confirmed"]

Yes no questions with Inquirer

Others useful links :

Inquirer's Github repo

Khopa
  • 2,062
  • 1
  • 23
  • 22
  • 1
    Oh, that's exactly what I meant. No way to make it work on windows? – Gianni Alessandro Jun 01 '16 at 12:27
  • It's probably possible, but i havent managed to make it work on windows so far. I didn't dig too much as i didn't needed it to work on windows when i used it... – Khopa Jun 01 '16 at 14:10
  • 4
    For Windows users, try `PyInquirer` which works great. And here is the link to their documentation: https://github.com/CITGuru/PyInquirer/ – ian0411 Jun 25 '19 at 17:23
  • https://github.com/wong2/pick also supports Windows, with a simple and small API – wong2 Dec 07 '21 at 03:28
8

One possible way to achieve what you appear to require is with a while loop.

print "Do you want to enter the door"
response = None
while response not in {"yes", "no"}:
    response = raw_input("Please enter yes or no: ")
# Now response is either "yes" or "no"
holdenweb
  • 33,305
  • 7
  • 57
  • 77
  • 1
    Just curious, is using a set for the options `{"yes", "no"}` better / faster than using a list? – Keatinge Jun 01 '16 at 10:30
  • 3
    In the two-element case it will make hardly any difference, but a set becomes much faster as the number of elements goes up. The reason for this is the underlying implementation of the `__contains__` method, which for a list iterates over each element until it finds a match or exhausts all the elements. For a set, hashing techniques are used to give almost constant execution time regardless of the number of elements. – holdenweb Jun 01 '16 at 10:43
8

For an OS agnostic solutions using prompt-toolkit 2 or 3, use questionary

https://github.com/tmbo/questionary

M.Vanderlee
  • 2,847
  • 2
  • 19
  • 16
8

This is a bit of an overkill for only selecting yes or no but it is a generic solution also working for more then two options. And it is protected for non existing options and will force the user to give a new valid input. This without any imports.

First a function which handles all functionality:

def selectFromDict(options, name):

index = 0
indexValidList = []
print('Select a ' + name + ':')
for optionName in options:
    index = index + 1
    indexValidList.extend([options[optionName]])
    print(str(index) + ') ' + optionName)
inputValid = False
while not inputValid:
    inputRaw = input(name + ': ')
    inputNo = int(inputRaw) - 1
    if inputNo > -1 and inputNo < len(indexValidList):
        selected = indexValidList[inputNo]
        print('Selected ' +  name + ': ' + selected)
        inputValid = True
        break
    else:
        print('Please select a valid ' + name + ' number')

return selected

Then a dict with all options

options = {}
#     [USER OPTION] = PROGRAM RESULT
options['Yes'] = 'yes'
options['No'] = 'no'

And then call the function with the options

# Let user select a month
option = selectFromDict(options, 'option')

The result is:

> Select a option:
> 1) Yes
> 2) No
> option: 3
> Please select a valid option number
> option: 1
> Selected option: yes

As said this is saleable to for instance all months of the year re-using the function above:

months = {}
months['January'] = 'jan'
months['February'] = 'feb'
months['March'] = 'mar'
months['April'] = 'apr'
months['May'] = 'may'
months['June'] = 'jun'
months['July'] = 'jul'
months['August'] = 'aug'
months['September'] = 'sep'
months['October'] = 'oct'
months['November'] = 'nov'
months['December'] = 'dec'

# Let user select a month
month = selectFromDict(months, 'Month')

Example result:

> Select a Month:
> 1) January
> 2) February
> 3) March
> 4) April
> 5) May
> 6) June
> 7) July
> 8) August
> 9) September
> 10) October
> 11) November
> 12) December
> Month: 5
> Selected Month: may
Wallem89
  • 304
  • 2
  • 14
3

For those in Python 3, and that want a non case sensitive option:

def ask_user():
    print("Do you want to save?")
    response = ''
    while response not in {"yes", "no"}:
        response = input("Please enter yes or no: ").lower()
    return response == "yes"

And, if I understand Assignment Expressions (PEP 572) correctly, in Python 3.8 you will be able to do this:

def ask_user():
    while r:= input("Do you want to save? (Enter yes/no)").lower() not in {"yes", "no"}:
        pass
    return r == "yes"
toto_tico
  • 17,977
  • 9
  • 97
  • 116
  • 1
    While the walrus formulation is superficially "neat," readability is also a factor, and the use of a `pass` loop body will, I suspect, increase cognitive load for most readers. A `while` loop with a real body and the right initial condition seemed the more natural formulation to me. There's always room for differences of opinion in programming. – holdenweb Aug 12 '21 at 10:35
  • 1
    In the first option, it's simpler to do `response = input(...).lower()`. Then you can also do `response = None` for a more solid sentinel value. – wjandrea Apr 05 '22 at 17:14
  • 1
    @wjandrea, I agree with the first part and modified accoridingly, but I am not sure about the `response=None` making it a more solid sentinel value, feel free to edit directly. – toto_tico Apr 11 '22 at 16:00
  • @toto_tico Nah, that's fine, on second thought, having a different type as a sentinel might have drawbacks. – wjandrea Apr 11 '22 at 17:01
0

If you use Windows and you need the immediate input with one character, the this should work:

import os

inp = "yn"

if os.system("choice /c:%s /n /m \"Yes or No (Y/N)\"" % inp) - 2:
    # if pressed Y
    print("Yes")
else:
    # if pressed N
    print("No")

P. S.: This code works on Python 3

Somebody
  • 115
  • 6