4
\$\begingroup\$

I'm into Python from only about a month, coming from nothing. I didn't make any complex program yet, but I'm almost there, as one is almost ready to be used, like if I'd sit there for another hour or so I could finish it. This is the first function I consider myself proud of, as it's the most complex one I created, for now, I created it because I needed something like this in the program. Let me know your thoughts.

def dynmultichoice(description: str, options: list, quitoption: bool = True) -> list:
    '''
    Creates a dynamic multi-choice question, meaning that the options may vary.\n 
    
    The description parameter is used as description, alias gets placed as the first line.\n
    
    The options parameter being a list where every item gets one line, made like this\n
     (<Option Number>) <Option>)\n

    The quitoption parameter enables (True) or disables (False) the option to cancel the question, 
    returning [0, 0].\n

    The function returns a list [a, b], where "a" is the name of the option, and "b" is the number 
    of the option.

    '''
    choices = f'{description}\n'
    endline = '['
    enm = list(enumerate(options, 1))
    for index, item in enm:
        choices = f"{choices} {index}) {item}\n"
        endline = f'{endline}{index}/'
    if quitoption == True:
        choices = f'{choices} Q) Quit\n'
        endline = f'{endline}Q]? : '
    else:
        endline = (f'{endline}/]? : ').replace('//', '')
    prompt = f'{choices}{endline}'
    while True:
        response = input(prompt)
        if quitoption == True and (response == 'Q' or response == 'q'):
            break
        try:
            int(response) + 1
            itsok = True
        except:
            itsok = False
        if itsok == True:
            if quitoption == True:
                lines = (prompt.count("\n")) - 2
            else:
                lines = (prompt.count("\n")) - 1
            if int(response) <= lines:
                break
            else:
                 print("Answer not valid.")
        else: 
            print("Answer not valid.")   
    if quitoption == True and (response == 'Q' or response == 'q'):
        return [0, 0]
    else:
        return [(enm[int(response) - 1])[1], int(response)]
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

I think you've over-thought this. Forming the prompt string in one shot isn't all that difficult, and there's only one place where the 'quit' functionality should deviate in logic.

Your type hints should be improved: options should not be a bare list, but instead a Sequence[str] since your function will also work with tuples.

The return typehint should not be a bare list, but instead a tuple with sub-types shown.

Function names should be action phrases. Instead of dynmultichoice, consider something like ask_multi_choice.

Function, parameter and variable names should be lower_snake_case, as in quit_option.

Avoid successive-format calls to build up strings; instead make use of join().

Rather than comparing to both q and Q, upper-case your input and then unconditionally compare.

It's not clear that there's any particular reason for returning integer indexes. If they're used as keys for a dictionary, the usage will be no different. In that case it's easier, rather than the number 0, to just use the letter Q for the quit key.

Suggested

def ask_multi_choice(description: str, *options: str, quit_option: bool = True) -> tuple[
    str,  # option name
    str,  # option key
]:
    """
    Prompts the user to answer a multi-choice question, with format:

    description
     1) option1
     2) option2
     3) option3
     Q) Quit
    [1/2/3/Q]? :

    Returns the option string chosen and its key. Keys are the option's one-based
    index as a string, or 'Q' for quit.

    If quit_option is True, the Q/Quit option is included.
    """

    choices = {
        str(key): item
        for key, item in enumerate(options, 1)
    }

    if quit_option:
        choices['Q'] = 'quit'

    prompt = (
        description
        + '\n'
        + '\n'.join(f' {key}) {item}' for key, item in choices.items())
        + '\n['
        + '/'.join(choices.keys())
        + ']? :'
    )

    while True:
        key = input(prompt).upper()
        name = choices.get(key)
        if name is not None:
            return name, key

        print("Answer not valid.")


choice, key = ask_multi_choice(
    'description',
    'option1',
    'option2',
    'option3',
    quit_option=True,
)

Output

description
 1) option1
 2) option2
 3) option3
 Q) quit
[1/2/3/Q]? :2
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.