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
isFalse
.next, if you want to use the conditions, you must set the bool
conditions_on_input
toTrue
, whose default value isFalse
.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 isTrue
.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
toTrue
makes it so that your must phrase must be completely lowercase, same withmust_be_uppercase
. NOTE: If you set bothmust_be_uppercase
andmust_be_lowercase
toTrue
, the program will raise an error.Those are all of the string conditions. The next keyword is
must_be_an_int
. If it isTrue
, it will NOT check any of the string conditions, it will only check the following integer conditions. If it isFalse
, (its default value), it will only check the string and not the ints.highest_range
andlowest_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
andmust_be_even
. They are self-explanatory. Once again, if both areTrue
, the program will raise an error.
I am thinking of ways to improve this and make this better, thanks for any feedback!
-
\$\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– Casey2020年11月08日 02:00:49 +00:00Commented Nov 8, 2020 at 2:00
-
\$\begingroup\$ @Casey You need to use keyword arguments for most of the variables... \$\endgroup\$fartgeek– fartgeek2020年11月08日 12:07:37 +00:00Commented Nov 8, 2020 at 12:07
3 Answers 3
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.
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.
-
5\$\begingroup\$ Adding to Reinderien's answer: It could be in a library that includes predefined predicates for numbers, Yes/No, etc. \$\endgroup\$RootTwo– RootTwo2020年11月06日 23:23:20 +00:00Commented Nov 6, 2020 at 23:23
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
.