Skip to content

Instantly share code, notes, and snippets.

@thelabcat
Created September 20, 2025 17:36
Show Gist options
  • Select an option

  • Save thelabcat/83c9e16b5eae020b17a2b93c9ff829f7 to your computer and use it in GitHub Desktop.

Select an option

Save thelabcat/83c9e16b5eae020b17a2b93c9ff829f7 to your computer and use it in GitHub Desktop.
Python module demo for kids - Multiple Choice CLI Question Asker
#!/usr/bin/env python3
"""Multiple choice CLI input
Present the user with a list of options, and let them choose either by name, or
numerically.
Copyright 2025 Wilbur Jaywright.
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program. If not, see <https://www.gnu.org/licenses/>.
S.D.G.
"""
# That first line with the #! is a shebang line for *NIX systems. If this
# script is set as an executable, it tells them what to execute it with,
# and where that executor is. In this case, it's the Python 3 interpreter.
# The next thing is a module docstring. It's an explanation of what the
# module's name is, what it does, who wrote it, and what the rules are for
# using or copying it. I also want to give credit to Whom credit is ALWAYS due:
# S.D.G. is a Latin acronym for, 'To God alone be the glory.'
# Our first bit of Python code should usually be any imports we need. For this
# module, we just need one: The Sequence class from the typing module. This
# 'typing' module isn't for your keyboard, but rather it lets us tell the
# programmer what type of Python object should be passed to the argument
# 'options' in our function: Anything we can subscript with an index.
from typing import Sequence
# Now that our imports are done, we can start defining things!
def multiple_choice(title: str, options: Sequence[str]) -> str:
"""Allow the user to choose between multiple options
Args:
title (str): The question at hand.
options (Sequence[str]): A subscriptable of option strings.
Returns:
choice (str): The chosen option."""
# The function can't work if there's no options to choose from!
# In Python, 'assert' means, "Make sure that this first thing is true, and
# raise an error with this second thing as a message if it isn't."
assert len(options) > 0, "Too few options"
# Find the 'biggest' option by length, and then find out how long it is.
# We'll use this when we display the question so that we can make it nice
# and pretty.
option_max_width = len(max(options, key=len))
# We're going to show a number next to each option as well, so we'd better
# get the visual length of the biggest number as well. That's easy: The
# biggest number is just the total number of options there are. So, we find
# that, turn it into a string, and then get the length of the string.
num_width = len(str(len(options)))
# Make sure we get a valid choice.
# The return statements will exit this loop for us.
while True:
# Display the question and the options, and get an input
print(title)
# i becomes the index of each option as we iterate through them, thanks
# to the enumerate function.
for i, option in enumerate(options):
# This is some fancy formatted string code, so let me explain.
# Everything within those curly brackets is evaluated before it is
# printed. Before the colon is what gets evaluated, and anything
# after the colon is instructions on how to print it. The first
# thing is to print the number of the option. Python indexes start
# at zero, so we add 1. After that, I wanted to format it so all
# the numbers are padded with zeros. You can see where I used
# num_width here. After two dots, I print the option, but I justify
# it to the right with more dots, so that the right-hand side of
# all the options is lined up.
print(f"{i + 1:0{num_width}d}··{option:·>{option_max_width}}")
# Finally, ask the user for some input.
entry = input("Choice: ")
# Option was typed directly
if entry in options:
return entry
# Number was typed
if entry.isnumeric():
try:
return options[int(entry) - 1]
# The number wasn't a valid option index
except IndexError:
print("Entered number does not match an option.")
# Something was typed but it was invalid
if entry:
print("Invalid entry. Please type a number or the option itself.")
# If this module is run directly, perform a basic test.
if __name__ == "__main__":
print("Multiple choice CLI input library")
print("Performing test...")
# Store some things that never change
QUESTION = "Which of the following is not a dessert?"
OPTIONS = "cookies", "cake", "brownies", "onions"
CORRECT = "onions"
# Make sure the correct answer is an option!
# If we ever change the program, we might forget to test this ourselves
assert CORRECT in OPTIONS, \
"The test question's correct answer isn't even an option"
# Ask the question
choice = multiple_choice(QUESTION, OPTIONS)
# Respond to the user's answer
print(f"You chose '{choice}'.")
if choice == CORRECT:
print("Correct!")
else:
print(f"But, the correct choice was '{CORRECT}'.")
# Wait for the user to exit the program
input("Press Enter to exit.")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment