Reputation: 165
I have a simple python (version 2.7.3) code that has an output that I can't figure out. The code prompts the user for a score (and will continue to do so if the input is anything other than a number from 0 to 1), determines the letter grade, and then exits. The code is as follows:
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 an 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 prints out the correct grade (B), but then script doesn't end as I expect it to. Instead, it prints out the error message for a non-numeric value, and then prompts the user to enter a score again. If I enter another valid score (0.7 in this case), then script prints out the correct grade (C), and then prints out the first incorrect input (1.5) along with its grade (A).
I can't, for the life of me, figure out what's causing this, "functionality". Any suggestions?
Upvotes: 3
Views: 254
Reputation: 320
First, you cannot call inside calc_grade()
. That will run a bunch of errors. You can only call it once, but you can print it as many times as you want. Second, try
and except
might not be the best way to do it. Try making a class
and making functions from there. try
and except
will be run every time your code finished running. Third, if you run a number between any of those numbers it will print
all the letter before the maximum. I have code that is similar to yours, it calculates 3 peoples scores. Here is a website to help you better understand Errors and Exceptions.
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))
Upvotes: 0
Reputation: 405
You are forgetting that recursion doesn't terminate the previous call to the function. So when you call calc_grade()
at the error you then return to the original calc_grade()
to print "the score is:"
and that's why it prints several times.
Now, to fix your code, I'd 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 return, you can use it to simply exit the function.
Also I recommend using str.format
as oposed to the %
formatting.
This is how I'd do it without modifying your 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()
Upvotes: 0
Reputation: 462
Here's what happened:
When you passed your function the value "h", the casting of "h" to float
failed, which threw a ValueError. Your except
statement caught the error, and then called calcGrade()
again. This new call was given an argument of .8, and returned normally. When the .8 call returned, it returned control back to the call the had received "h" as an argument. That call then proceeded to execute its next instruction: print "\nthe score is: %s" % (score)
. Since the cast to float had failed, score was never assigned. Therefore, that call to calcGrade()
throws an UnboundLocalError
, which is then caught by its caller, which is the instance of calcGrade()
that was passed the value 1.5 (as @ZackTanner pointed out). Recall that the "h" call was called from inside the try
block.
Upvotes: 1
Reputation: 1450
Recusion is biting you because you have additional code in your function after the recursion. @Mureink 's answer is a valid way to handle this. Another is to make the data input action it's own function:
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 technique returns the entered value when the user enters a valid value. When they do not, it returns the value of calling get_input()
. this stacks up all the recursions ready to return whatever gets returned to them. When the user finally enters a valid response, the entire recursion stack collapses returning the valid answer that the user entered.
The call to get_input()
within calc_grade
will process until the user enters a valid answer. At that time get_input
will cease processing and return that valid user input to calc_grade
for calc_grade
to do its processing.
Upvotes: 0
Reputation: 312259
On any error that occurs, you call calc_grade
recursively again, so if you entered an invalid input, you'd have several calls. Instead, you should handle faulty 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
Upvotes: 7