Simple Python code error
I have some simple python code (version 2.7.3) that has an output that I cannot understand. The code asks the user for a rating (and will continue to do so if the input is anything other than a number between 0 and 1), determines the letter class, and then exits. The code looks like this:
def calc_grade():
try:
score = float(raw_input("Enter a score: "))
if score > 1.0:
print "Error: Score cannot be greater than 1."
calc_grade()
except:
print "Error: Score must be a numeric value from 0 to 1."
calc_grade()
print "\nthe score is: %s" % (score)
if score >= 0.9:
print "A"
elif score >= 0.8:
print "B"
elif score >= 0.7:
print "C"
elif score >= 0.6:
print "D"
else:
print "F"
return 0
calc_grade()
If I run this script try the inputs: 1.5, h, 0.8, then I get the following output:
Enter a score: 1.5
Error: Score cannot be greater than 1.
Enter a score: h
Error: Score must be a numeric value from 0 to 1.
Enter a score: 0.8
the score is: 0.8
B
Error: Score must be a numeric value from 0 to 1.
Enter a score: 0.7
the score is: 0.7
C
the score is: 1.5
A
As you can see, after entering a valid value (0.8), the script outputs the correct score (B), but then the script does not end as I expect. Instead, it prints an error message for a non-numeric value and then prompts the user to re-enter the account. If I enter another valid score (in this case 0.7), then the script prints out the correct grade (C) and then prints out the first invalid input (1.5) along with its class (A).
I cannot, for the life of me, figure out what is causing this, "functionality". Any suggestions?
source to share
If any error occurs, you are calling calc_grade
recursively again, so if you enter invalid input, you will have multiple calls. Instead, you should be able to handle erroneous errors iteratively:
def calc_grade():
score = None
while score is None:
try:
score = float(raw_input("Enter a score: "))
if score > 1.0:
print "Error: Score cannot be greater than 1."
score = None
except:
print "Error: Score must be a numeric value from 0 to 1."
# If we reached here, score is valid,
# continue with the rest of the code
source to share
Here's what happened:
When you passed the value "h" to your function, casting "h" before float
failed, resulting in a ValueError. Your operator except
caught the error and then called again calcGrade()
. This new call received an argument .8 and returned fine. When the call to .8 returned, it returned control back to the call that received "h" as an argument. Then the challenge run the following command: print "\nthe score is: %s" % (score)
. Since the lead to swimming failed, a score was never assigned. So this call calcGrade()
throws UnboundLocalError
, which is then captured by its caller, which is the instance calcGrade()
that was passed the value 1.5 (as @ZackTanner pointed out). Recall that the call to "h" was called from within the block try
.
source to share
Recusion bites you because after the recursion you have additional code. @ Mureink's answer is the correct way to handle this. The other is to execute the data entry function that it does:
def get_input():
try:
score = float(raw_input("Enter a score: "))
if score > 1.0:
print "Error: Score cannot be greater than 1."
return get_input()
except ValueError:
print "Error: Score must be a numeric value from 0 to 1."
return get_input()
return score
def calc_grade():
score = get_input()
print "\nthe score is: %s" % (score)
if score >= 0.9:
print "A"
elif score >= 0.8:
print "B"
elif score >= 0.7:
print "C"
elif score >= 0.6:
print "D"
else:
print "F"
return 0
calc_grade()
This method returns the entered value when the user enters a valid value. When they don't, it returns the call value get_input()
. this sums up all the recursions ready to return whatever is returned to them. When the user finally enters the correct answer, the entire recursion stack collapses, returning the valid answer as entered by the user.
The call get_input()
inside calc_grade
will continue until the user enters a valid answer. At this time, it get_input
will stop processing and return this valid user input calc_grade
to calc_grade
for processing.
source to share
You forget that recursion does not terminate the previous function call. So when you call calc_grade()
on error, you go back to the original calc_grade()
in print "the score is:"
and therefore print it multiple times.
Now, to fix your code, I'll just add some returs:
def calc_grade():
try:
score = float(raw_input("Enter a score: "))
if score > 1.0:
print "Error: Score cannot be greater than 1."
calc_grade()
return
except:
print "Error: Score must be a numeric value from 0 to 1."
calc_grade()
return
print "\nthe score is: %s" % (score)
if score >= 0.9:
print "A"
elif score >= 0.8:
print "B"
elif score >= 0.7:
print "C"
elif score >= 0.6:
print "D"
else:
print "F"
calc_grade()
Python doesn't require you to write anything after returning, you can use it to simply exit the function.
Also I recommend using it str.format
as a formatting option %
.
This is how I would do it without changing the code too much:
def calc_grade():
try:
score = float(raw_input("Enter a score: "))
if score > 1.0:
raise TypeError
except ValueError:
print "Error: Score must be a numeric value from 0 to 1."
except TypeError:
print "Error: Score cannot be greater than 1."
except:
print "Error: Unexpected error, try again."
else:
if score >= 0.9:
score = "A"
elif score >= 0.8:
score = "B"
elif score >= 0.7:
score = "C"
elif score >= 0.6:
score = "D"
else:
score = "F"
print "the score is: {}".format(score)
return
calc_grade()
calc_grade()
source to share
At first , you cannot call inside calc_grade()
. This will lead to many errors. You can only call it once, but you can print it as many times as you like. Second , try
and except
perhaps not the best way to do this. Try to make class
and make functions from there. try
and except
will run every time your code is run. Third , if you run a number between any of these numbers, it will be print
all letters up to the maximum. I have a code similar to yours, it calculates 3 people counts. Here is a website to help you understand errors and exceptions better.
https://docs.python.org/2/tutorial/errors.html
Here is my code
lloyd = {
"name": "Lloyd",
"homework": [90.0, 97.0, 75.0, 92.0],
"quizzes": [88.0, 40.0, 94.0],
"tests": [75.0, 90.0]
}
alice = {
"name": "Alice",
"homework": [100.0, 92.0, 98.0, 100.0],
"quizzes": [82.0, 83.0, 91.0],
"tests": [89.0, 97.0]
}
tyler = {
"name": "Tyler",
"homework": [0.0, 87.0, 75.0, 22.0],
"quizzes": [0.0, 75.0, 78.0],
"tests": [100.0, 100.0]
}
# Add your function below!
def average(numbers):
total = sum(numbers)
total = float(total)
return total/len(numbers)
def get_average(student):
homework_ave=average(student["homework"])
quizzes_ave=average(student["quizzes"])
tests_ave=average(student["tests"])
return 0.1 * average(student["homework"]) + 0.3 * average(student["quizzes"]) + 0.6 * average(student["tests"])
def get_letter_grade(score):
if 90 <= score:
return "A"
elif 80 <= score:
return "B"
elif 70 <= score:
return "C"
elif 60 <= score:
return "D"
else:
return "F"
print get_letter_grade(get_average(lloyd))
def get_class_average(students):
results = []
for student in students:
results.append(get_average(student))
return average(results)
students = [lloyd, alice, tyler]
print get_class_average(students)
print get_letter_grade(get_class_average(students))
source to share