Search code examples
pythonif-statementwhile-loopuser-input

User input - validation and confirmation -


I am trying to perform something that has certainly been asked several times before (specifically Asking the user for input until they give a valid response, but I'm either trying to do something too complex or not thinking about my loops the correct way.

In the code below, I am trying to do a few things:

  • Ask the user for a list of integers, could be one or several (in this case, loan_ids)
  • If the integer fails, reject the input loudly
  • if an integer, append to a running list
  • Convert all results to tuples to they can be used in a SQL query

In the second func, I am trying to combine the first one and then also ask the user to confirm the loan_ids before cutting out:

  • If the user enters y, then end the loop
  • If the user enters n, go back to the first function and ask again
  • If anything else is entered, ask the user to resubmit the answer

I'm trying not to violate the Don't Repeat Yourself principle, but for the life of me I can't figure out the placement of the loops. I'm also relatively new to python and programming structure, so open to idiomatic comments on the design as well

def get_unique_loans_from_user():
    """Get list of  loan_ids from user

    This function is mean to capture raw loan_ids a user wishes to look up. 

    Returns tuple of unique loan_ids

    """

    loan_ids = []
    while True:    
        loan_id = raw_input('> Loan Id: ')
        # If nothing entered, exit
        if len(loan_id)==0:
            break
        # Make sure an integer was entered      
        else:
            try:
                int(loan_id)
            except ValueError:
                print loan_id + " is not a real integer, so likely not a valid loan id"
                continue
        # if an integer was entered, append to running list as an integer and continue
        # We only need to return the unique list of loan ids
            else:
                loan_ids.append(int(loan_id))

    # Convert unique list of loans to tuple to use in SQL     
    loans = tuple(np.unique(loan_ids))   

    # Return the tuple of loans
    return loans

And the second piece - the way it's currently written forces the same result when a user enters anything but y - I'm trying to cause different behavior depending on whether the use has invalid input vs. actually confirms the loan_ids are incorrect. I also have used an extra break statement at the end, which I'm fairly sure is not best practice

def confirm_loan_ids():
    """ Confirm the list of loan ids is what the user wanted"""

    while True:

        # Get loan ids from user through raw input
        ids = get_unique_loans_from_user()

        # Print out the ids to the user
        print "Printing loan ids. There are {0} ids".format(len(ids))
        print ids

        # Confirm with user these are the correct ids
        answer = raw_input('> Are these the loan ids you expected? [y/n] ')


    # If user confirms correct, and continue
        if answer == 'y':
            print "\nExcellent - moving on\n"
            print "Here are your loan ids:"
            print ids
            break
    # If the answer is n, repeat the question
        elif answer == 'n':
            print "\n-------------> Let\'s try again\n"
            continue
    # If answer is not no or yes, ask again    
        elif (len(answer) == 0) or (answer not in ('y','n')):
            print "Please enter only y or n"
            continue
        else:
            print "This is only a test"
        # The If loop only breaks when the answer is 'y'
        break

    return ids

Solution

  • Try this. It reduces some of the requirements to use continue, instead letting the loop naturally iterate again. This reduces visual noise in the code.

    For your second function, you can again simplify the logic to remove the need to use continue, instead only breaking when the input is good.

    The code needs to loop again for the yes/no confirmation, so to avoid issues with having to break out of nested loops, the yes/no loop is in another function. This function won't return until a valid input is received.

    Hopefully this gives you some slightly clearer logic to work with?

    You can reinstate your numpy.unique function if you want instead of set, but I don't generally use numpy and importing it just for that purpose seems a bit excessive I used set instead. I don't know your exact requirements, so don't know if set or numpy.unique is more appropriate.

    from __future__ import print_function
    
    def get_unique_loans_from_user():
    
        loan_ids = []
    
        while True:
            loan_id = raw_input('> Loan Id: ')
    
            try:
                loan_id_int = int(loan_id)
    
                if loan_id_int == 0:
                    break
    
                loan_ids.append(loan_id_int)
            except ValueError:
                print("%s is not a real integer, so likely not a valid loan id" % loan_id)
    
        # note: this will alter the order.
        return tuple(set(loan_ids))
    
    
    def yesnoconfirm(msg):
    
        while True:
            answer = raw_input(msg)
    
            if answer in ('y', 'n'):
                return answer
            else:
                print('not a valid input')
    
    
    def confirm_loan_ids():
    
        while True:
    
            ids = get_unique_loans_from_user()
    
            print("Printing loan ids. There are {0} ids".format(len(ids)))
            print(ids)
    
            answer = yesnoconfirm('> Are these the loan ids you expected? [y/n] ')
    
            if answer == 'y':
                break
            elif answer == 'n':
                print("\n-------------> Let\'s try again\n")
            else:
                print("oops something went wrong")
    
        print("\nExcellent - moving on\n")
        print("Here are your loan ids:")
        print(ids)
    
        return ids
    
    print(get_unique_loans_from_user())
    raw_input('continue?')
    confirm_loan_ids()