9
\$\begingroup\$

Have you ever written a python script, then got to a part where you have to get input from the user? I would assume your code went something like this:

user_input = int(input("Enter number: "))

Then you realized that if the player enters "keyboard spam", then your program crashes. So you need to set up a while-loop exception type thing, like this:

while True:
    try:
        user_input = int(input("Enter number: "))
        break
    except:
        print("you screwed up, only number allowed")

Then, you realize that your inputs must be in between 5 and 23 for the features of your program, so you just code that in too... All in all, this is taking a while and is a pain in the neck!!! What you need is a function that already does all of this for you!

Enter my function:


def get_input(prompt,
              # string conditions
              *conditions, check_alpha=False,   must_be_lowercase=False, must_be_uppercase=False,
              conditions_on_input=False, all_conditions_must_be_true=True,
              # now int conditions
              must_be_an_int=False, highest_range=100000, lowest_range=-100000, must_be_odd=False, must_be_even=False):
    if must_be_uppercase and must_be_lowercase:
        raise ValueError("The word cannot be both uppercase and lowercase!!!")
    if must_be_even and must_be_odd:
        raise ValueError("The word cannot be both even and odd!!!")
    while True:
        word_valid = True
        try:
            user_input = input(prompt)
            if must_be_an_int:  # if the value must be an int
                try:
                    number = int(user_input)
                    if not lowest_range < number < highest_range:
                        print(f"Values must be between {lowest_range} and {highest_range}!")
                        word_valid = False
                        continue
                    else:  # the number is between the specified range
                        if must_be_even:
                            if not number % 2 == 0:
                                print("Number must be even!")
                                word_valid = False
                                continue
                        elif must_be_odd:
                            if number % 2 == 0:
                                print("Number must be odd!")
                                word_valid = False
                                continue
                except Exception:
                    print("The word must be an integer!", user_input)
                    word_valid = False
                    continue
                if word_valid:
                    return user_input

            else:  # if the value must be a string
                if check_alpha:  # here we have a boolean. If it is true, we check to make sure only letter are in the
                    # user input
                    if not user_input.isalpha():
                        print("Only alphabetic characters allowed!")
                        word_valid = False
                        continue

                if conditions_on_input:
                    if all_conditions_must_be_true:
                        for condition in conditions:
                            if condition not in user_input:
                                print(f"You have not satisfied the condition that there must be '{condition}!'")
                                word_valid = False
                                continue
                    else:
                        condition_found = False
                        for condition in conditions:
                            if condition in user_input:
                                condition_found = True
                                break
                        if not condition_found:
                            print(f"You must satisfy one of the following conditions: ", end="\n")
                            for condition in conditions:
                                print("'", condition, "'", sep="")
                            print()
                            word_valid = False
                            continue


                if must_be_lowercase:
                    for i in user_input:
                        if not i.islower():
                            print("Letters must be lowercase! Try again!")
                            word_valid = False
                            continue
                elif must_be_uppercase:
                    for i in user_input:
                        if not i.isupper():
                            print("Letters must be uppercase! Try again!")
                            word_valid = False
                            continue
            if word_valid:
                return user_input

        except Exception:
            print("No funny business!! Try again!")

Although my function may be a bit chunky, it gets the job done. Note: All of the below features you use as parameters, not modifications to the function itself...

Features:

  • first you put in the prompt, nothing special here

  • next you put in letters (or numbers) you want in the program, each of those as a positional argument. These get classified as conditions. These only apply if the boolean must_be_an_int is False.

  • next, if you want to use the conditions, you must set the bool conditions_on_input to True, whose default value is False.

  • There are two ways to use the conditions. There is a) all of the conditions must be met, or b) only one of the conditions must be met. The way to toggle this is by use of the keyword all_conditions_must_be_true. It's default value is True.

  • Using the keyword check_alpha you can check whether or not the value must only be in the alphabet.

  • Setting the keyword must_be_lowercase to True makes it so that your must phrase must be completely lowercase, same with must_be_uppercase. NOTE: If you set both must_be_uppercase and must_be_lowercase to True, the program will raise an error.

  • Those are all of the string conditions. The next keyword is must_be_an_int. If it is True, it will NOT check any of the string conditions, it will only check the following integer conditions. If it is False, (its default value), it will only check the string and not the ints.

  • highest_range and lowest_range, respectively are the highest and lowest ranges that your number can be. They are by default, one hundred thousand and negative one hundred thousand.

  • must_be_odd and must_be_even. They are self-explanatory. Once again, if both are True, the program will raise an error.

I am thinking of ways to improve this and make this better, thanks for any feedback!

\$\endgroup\$
2
  • \$\begingroup\$ I haven't used Python in a few years, does it provide a type system where all those boolean arguments can be encapsulated into an "options" type argument that is declared beforehand and just passed in? In my experience multiple boolean arguments one after the other will get messed up at some point. \$\endgroup\$ – Casey Nov 8 '20 at 2:00
  • \$\begingroup\$ @Casey You need to use keyword arguments for most of the variables... \$\endgroup\$ – fartgeek Nov 8 '20 at 12:07
7
\$\begingroup\$

Be specific when catching exceptions, and include only the essential, relevant code inside a try-except!

For example:

            if must_be_an_int:  # if the value must be an int
                try:
                    number = int(user_input)
                    if not lowest_range < number < highest_range:
                        print(f"Values must be between {lowest_range} and {highest_range}!")
                        word_valid = False
                        continue
                    else:  # the number is between the specified range
                        if must_be_even:
                            if not number % 2 == 0:
                                print("Number must be even!")
                                word_valid = False
                                continue
                        elif must_be_odd:
                            if number % 2 == 0:
                                print("Number must be odd!")
                                word_valid = False
                                continue
                except Exception:
                    print("The word must be an integer!", user_input)
                    word_valid = False
                    continue
                if word_valid:
                    return user_input

The purpose of the try-except is to check that the input is an int! As it stands, the code catches a bunch of random exceptions, and then treats them all as if they meant that the input isn't an int.

            if must_be_an_int:  # if the value must be an int
                try:
                    number = int(user_input)
                except ValueError:
                    print("The word must be an integer!", user_input)
                    word_valid = False
                    continue
                if not lowest_range < number < highest_range:
                    print(f"Values must be between {lowest_range} and {highest_range}!")
                    word_valid = False
                    continue
                else:  # the number is between the specified range
                    if must_be_even:
                        if not number % 2 == 0:
                            print("Number must be even!")
                            word_valid = False
                            continue
                    elif must_be_odd:
                        if number % 2 == 0:
                            print("Number must be odd!")
                            word_valid = False
                            continue
                if word_valid:
                    return user_input

That code snippet above can be further improved, by removing the superfluous word_valid variable.

            if must_be_an_int:  # if the value must be an int
                try:
                    number = int(user_input)
                except ValueError:
                    print("The word must be an integer!", user_input)
                    continue
                if not lowest_range < number < highest_range:
                    print(f"Values must be between {lowest_range} and {highest_range}!")
                    continue
                else:  # the number is between the specified range
                    if must_be_even:
                        if not number % 2 == 0:
                            print("Number must be even!")
                            continue
                    elif must_be_odd:
                        if number % 2 == 0:
                            print("Number must be odd!")
                            continue
                return user_input

The if statement

if not number % 2 == 0:

can be simplified to

if number % 2 != 0:

which also makes it less ambiguous.


            if must_be_lowercase:
                for i in user_input:
                    if not i.islower():
                        print("Letters must be lowercase! Try again!")

It's best to keep names like i or j to refer to indices.

Also, you can use str.islower() on the entire string at once, which means the loop is superfluous.

\$\endgroup\$
11
\$\begingroup\$

As a toy to experiment with Python, fine. Please, please don't use this in real life. It's both impossible and counter-productive to try and predict every user input validation scenario, and you're better off writing and using only what you need.

Anyway, specific Python concerns:

condition not in user_input suggests that condition should really just be called substring or needle.

In this loop:

                for i in user_input:
                    if not i.isupper():
                        print("Letters must be uppercase! Try again!")
                        word_valid = False
                        continue

Your continue should probably be a break; otherwise you're going to output that error message for every single lowercase letter.

If you want to attempt something that is both simpler and more powerful, accept an iterable of tuples, each a predicate callback and an error message. On every iteration, call every callback, and if it fails, output its error message. If none of the callbacks fails, accept and return the input. Delete all of your range-checking, case-checking and alphanumeric checking logic.

\$\endgroup\$
1
  • 5
    \$\begingroup\$ Adding to Reinderien's answer: It could be in a library that includes predefined predicates for numbers, Yes/No, etc. \$\endgroup\$ – RootTwo Nov 6 '20 at 23:23
4
\$\begingroup\$

Suggestion: use an appropriate library

There is an amazing amount of Python libraries with tons of usefull functionality. Also for input processing. You can search PyPi for libraries. I found PyInputPlus actively maintained and pretty suitable for your requirements. The function inputInt covers almost all of it.

Your code could (almost!) be reduced to:

import pyinputplus as pyip
user_input = pyip.inputInt(prompt='Enter a number between 5 and 23: ', min=5, max=23)

Checking even and odd values might be done with regexes, which is also supported by inputInt.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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