Shaun
Shaun

Reputation: 165

Error with simple Python code

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

Answers (5)

drewteriyaki
drewteriyaki

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

izxle
izxle

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

Sam Hazleton
Sam Hazleton

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

Alan Hoover
Alan Hoover

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

Mureinik
Mureinik

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

Related Questions