I'm making a basic BMI calculation program for a class assignment using TKinter for the GUI, and ran into a problem when trying to validate the user's input.
I'm trying to only allow numerical input and to deactivate the 'calculate' button and send an error message when the user enters anything that's not a number. However, at the minute it will throw up an error for a single digit number (e.g. 2) but will accept multiple digits (e.g. 23). I'm quite new to this so could you please explain why this is happening, or if there's a better way to write this?
Here are the relevant parts of my code:
#calculate button
cal = ttk.Button(main, text = 'Calculate!')
cal.grid(row = 4, column = 2)
#height entry box
hb = tk.Entry(main, textvariable = height)
hb.grid(row = 2, column = 2)
hb.bind('<Key>', lambda event: val(hb.get()))
#validation error message
vrs = tk.Label(main, text = 'Please enter a number in the box')
vrs.grid(row = 8, column = 2)
#so that its position is saved but won't appear until validation fails
vrs.grid_remove()
#validation function
def val(value):
if value.isdigit():
print('valid')
vrs.grid_remove()
cal.state(['!disabled'])
else:
print('invalid')
vrs.grid()
cal.state(['disabled'])
Thanks in advance for your help.
The first thing you should do to debug this is to print out value inside of val, to see if your assumptions are correct. Validating your assumptions is always the first step in debugging.
What you'll find is that your function is being called before the digit typed by the user is actually inserted into the widget. This is expected behavior.
The simple solution is to put your binding on <KeyRelease>, since the default behavior of inserting the character is on <KeyPress>:
hb.bind('<Any-KeyRelease>', lambda event: val(hb.get()))
Even better would be to use the Entry widget's built-in validation features. For an example, see https://stackoverflow.com/a/4140988/7432
You need to use isdigit on strings.
val = '23'
val.isdigit() # True
val = '4'
val.isdigit() # True
val = 'abc'
val.isdigit() # False
If you're not sure what the type of the input is, cast it first to a string before calling isdigit().
If you want only one-digit numbers, you'll have to check if int(val) < 10
isdigit is a string method. Are you expecting a string, an int, or a float?
You can add some typechecking code like this, so that your program validates regardless of whether the value is a numerical type or a string type.
def val(value):
if type(value) in (int, float):
# this is definitely a numerical value
elif type(value) in (str, unicode, bytes):
# this is definitely a string
Related
I have a command line console game where I'm trying to capture integers from the User (the game is for learning times tables). I built a re-usable function to handle this which is working for blank answers, however will break if the User enters a string (e.g. "abc").
Can someone help me extend this function to filter out strings as well? (e.g. they should be asked to enter a number). I believe the error is being caused by the casting of int() in the return statement, however I need the input to be an Int for answer-checking comparison purposes (e.g. I compare their input to a stored answer, for which I need an Int == Int comparitor).
Function:
# gets a non-blank int from the User, printing a prompt and optionally displaying an error prompt
def get_int(prompt, err_prompt):
input_int = ""
# if entry is blank
while not input_int:
try:
# enter input
input_int = input(prompt)
if not input_int:
raise ValueError(err_prompt)
except ValueError as err:
print(err)
return int(input_int)
Calling the function example:
# get a non-blank int from the User as a guess
# note 'current_q' is a string from a list, e.g. "2 x 2 = " which acts as the prompt
guess = get_int(current_q, "You don't have a lot of other options. Try guessing a number...\n")
Example of the code working for blank answers:
Example of the code not working for strings:
Your if statement is wrong here:
if not input_int:
raise ValueError(err_prompt)
It will not raise an error if the value is not an empty string since its 'truthy'.
Instead you need to manually convert the input to an int and catch error:
def get_int(prompt, err_prompt):
input_int = ""
# if entry is blank
while not input_int:
try:
input_int = int(input(prompt)) # convert input to int
except ValueError:
print(err_prompt) # if the value can't be converted simply print your existing error prompt
return input_int
You could use isdigit on the input to check if it's an int before you cast it.
def get_int(prompt:str, error:str):
while not (u:=input(prompt)).isdigit():
print(error)
return int(u)
lets say i'm showing some data to user , i want user to be able to perform some sort of filtering on a numeric field in the database using a GET form so i have something like this
code = request.GET.get('code')
condition = {}
if( code is not None and int(code) > 0 ):
condition['code'] = int(code)
Somemodel.objects.filter(**condition)
but this works only if i code contains a number otherwise i get this error
invalid literal for int() with base 10: ''
so what is the pythonic way to handle this problem ? should i use try/except block? i perfer to handle this in the same if statement considering i might add other filters
isnumeric could check if code can be cast to int and also check that code is positive (when converted to an integer) thus replacing int(code) > 0:
if code is not None and code.isnumeric():
condition['code'] = int(code)
You should use a Django form with one or more IntegerFields; they do this conversion for you, then you can get the result from cleaned_data.
This function convert GET params to python numeric/bool accordingly:
def convert_params_to_int_or_bool(params):
for k, v in params.items():
if v.isnumeric():
params[k] = int(v)
if v == 'false':
params[k] = False
if v == 'true':
params[k] = True
The reason you are getting this error because int() expects the value in number it can be in the form string but it should be a number like "22","33" etc are valid .
But in your case you are passing empty that's why its raising an error. You can achieve your desired output using type(), it helps you in checking the type of number
So the modified code is
code = request.GET.get('code')
condition = {}
if( code is not None and type(code) == int ):
condition['code'] = int(code)
Somemodel.objects.filter(**condition)
Hope it helps :)
every time the below code runs, it goes straight through to the first else statement, then runs the next else statement four times. what is going on and how do I fix it so it calls the movement() module?
class square(object):
def __init__(self,updown,leftright,residence,name):
self.updown = updown
self.leftright = leftright
self.residence = bool
self.name = name
a1 = square(1,1,True,"a1")
a2 = square(2,1,False,"a2")
b1 = square(1,2,False,"b1")
b2 = square(2,2,False,"b2")
square_dict = {a1:"a1",a2:"a2",b1:"b1",b2:"b2"}
movement_select()
def movement_select():
response = raw_input("where would you like to move?")
if response in square_dict:
moveTo = square_dict[response]
else:
print "this runs once"
for a in square_dict:
if a.residence == True:
moveFrom = a
movement(moveFrom,moveTo)
else:
print "this runs four times"
movement_select()
Look at how you're defining residence:
self.residence = bool
So, for any square a, a.residence will be the type bool, never the boolean value True (or anything else). So this test will always fail:
if a.residence == True:
To fix it, change that first line to:
self.residence = residence
While we're at it, you rarely need == True, so you can also change the second line to:
if a.residence:
But that isn't a necessary fix, just a way of simplifying your code a bit.
Meanwhile, your squares_dict is a bit odd. I'm not sure whether it's incorrect or not, but let's take a look:
It maps from square objects to their names. That could be a useful thing to do. It means you can iterate over the dict and get all the squares—as you correctly do in your code. And if you later had a square and wanted to get its name, you could use square_dict for that. Then again, you could probably get the same benefit with just a square_list, and using the name already available as an attribute of the square objects (unless you need the same square to have different names in different contexts).
And meanwhile, a mapping in this direction can't be used for looking up squares_dict[response], because response is a name, not a square. So, you definitely need a mapping in the opposite direction, either in addition to or instead of this one.
If you scrap the square-to-name mapping and only keep the name-to-square mapping, you can still iterate over the squares; you'd just have to do for square in squares_dict.values(): instead of just for square in squares_dict:.
First problem: your dictionary appears to be backwards: you want to look up the square objects from their locations, rather than the other way around. This is why your first conditional is never true. You also might as well strip() the response to ensure that you don't have any hidden whitespace in there.
square_dict = {"a1":a1, "a2":a2, "b1":b1, "b2":b2}
# Later...
response = raw_input("where would you like to move?").strip()
# Later still...
for a in square_dict.values(): # Because we switched the dictionary around!
If you don't want to silently strip off the whitespace, I'd suggest that you at least echo their input back to them (print('"{}"'.format(response))) in the case that it's not found in your dictionary, so they (you) can be sure that at least the input was correct.
The second problem is because of how you define residence. You set the variable equal to bool, which is not what you want at all. Line five ought to read:
self.residence = residence
Finally, some other thoughts on your code! You check whether a value is True by checking if a.residence == True:. The preferred form of this comparison is the simpler version:
if a.residence:
Your methods could also be named more descriptively. Generally speaking, it's always nice to begin a function or method name with a verb, to improve readability. This is of course a question of style, but for instance, the two functions we see, movement_select and movement aren't extremely clear as to their function. It would be much easier to read if they used a standardized form, e.g. choose_move and perform_move.
self.residence = bool
should be
self.residence = residence
You don´t set residence right, this is wrong:
class square(object):
def __init__(self,updown,leftright,residence,name):
self.updown = updown
self.leftright = leftright
self.residence = bool
self.name = name
it has to be
class square(object):
def __init__(self,updown,leftright,residence,name):
self.updown = updown
self.leftright = leftright
self.residence = residence # not bool!!
self.name = name
Your response contains \n symbol, just strip() it
You should also swap places between keys and values in your dictionary
I'm trying to make a very basic calculator to familiarize myself with the basics of python. Part of the code involves asking for inputs and setting those as different variables, but the variables put in as inputs are stored as strings, even though they're entered as numbers:
def change_x_a():
velocity_i = input("Initial Velocity?")
velocity_f = input("Final Velocity?")
time = input("Time?")
float(velocity_i)
float(velocity_f)
float(time)
answer = (0.5*(velocity_i+velocity_f)*time)
print(answer)
Is there a fix for this?
float() doesn't modify the variable you pass it. Instead, it converts the value you give it and returns a float.
So
float(velocity_i)
by itself does nothing, where
velocity_i = float(velocity_i)
will give the behavior you're looking for.
Keep in mind that float() (and the other type-conversion functions) will throw an exception if you pass them something they're not expecting. For a better user experience, you should handle these exceptions1. Typically, one does this in a loop:
while True:
try:
velocity_i = float(input("Initial Velocity?"))
break # Valid input - stop asking
except ValueError:
pass # Ignore the exception, and ask again
We can wrap this behavior up into a nice little function to make it more re-usable:
def get_input(prompt, exptype):
while True:
try:
return exptype( input(prompt) )
except ValueError:
pass # Ignore the exception, and ask again
and call it like this:
val_f = get_input('Give me a floating-point value:', float)
val_i = get_input('Give me an integer value:', int)
1 - Wow, I just realized that I independently wrote almost the exact same code as the Python tutorial, which I linked to, after the fact.
You can convert the inputs to float when you take them from the user.
Try
velocity_i = float(input("Initial Velocity?")
And so on.
Yes. Simply convert it to a float:
velocity_i = float(input("Initial Velocity?"))
or an integer:
velocity_f = int(input("Final velocity?"))
I'm implementing a little UserOptionHandler class in python. It goes something like this:
class OptionValue():
__init__(self, default_value, value_type=None):
self.value=default_value
if value_type == None:
self.value_type = type( default_value )
else:
self.value_type = value_type
class OptionHandler():
__init__(self, option_list):
self.options = {}
for opt_spec in option_list:
key, value, value_type = opt_spec
self.options[key] = Option( value, value_type )
def set(self, key, value):
opt = self.options[key]
opt.value = value
When I get user input to set the value for an option, I want to make sure that they've entered sane. Otherwise, the application run until it gets to a state where it uses the option, which may or may not crash it.
How do you do something like the following?
opt = handler.get( key )
user_input = input("Enter value for {0}:".format(key) )
if( magic_castable_test( user_input, opt.value_type ) ):
print "Your value will work!"
else:
print "You value will break things!"
i'm not sure i follow what you're asking, but i think that you just need to use type itself because:
user input is a string
many types, like int and float are functions/constructors that return an instance from a string. for example int("3") returns the integer 3.
so in your code, you would replace
if( magic_castable_test( user_input, opt.value_type ) ):
print "Your value will work!"
else:
print "You value will break things!"
with:
value = None
try:
value = opt.value_type(value)
except:
pass
if value is None:
print "Your value will work! It is", value
else:
print "Your value will not work
where i am assuming it's an error if either (1) an exception is thrown or (2) the type returning null.
and you can take this further and simply require that any types work this way. so, for example, you might have some information that is best represented as a custom class. then you should write the class so that it has an __init__ that takes a single string:
class MyClass:
def __init__(self, s):
# do something with string s here
and you can then pass MyClass as an option type to your code above.
ps here's perhaps a clearer example of what i mean:
>>> type(4)("3")
3
>>> type(type(4)("3"))
<type 'int'>
Put every separate type (of user options) into separate class!
Add methods that decide if raw user input can be expressed by type "tied" to class.
Make Option handler run raw user input through list of all possible types - classes.
' If any class say that raw user input can be translated into its value than you know that user input is valid. If more than one class can store user input, then there is ambiguity, and you did provided too little info to suggest you anything then.
'' If no class claim ability to store user input, then you know that user provided wrong input!
Get back to your methods and make sure that they are malicious-user-proof.
If validation is simple you can use re module (regular expresion) to quickly and simply extract valid values. However if validation is more complex then you need to implement it by other means (since complex regular expressions are ... complex)
are you looking for isinstance(user_input, opt.value_type)?
http://docs.python.org/library/functions.html#isinstance