HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Get letter grade using eval()

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
gradegetusinglettereval

Problem

I understand that using eval is extremely dangerous as it can execute deadly commands as an input by the user.

I'm trying to write a code to get a letter grade from a number between 0 to 1. I was trying to write it in the most elegant way. Is this safe?

try:
    score = float(raw_input('Enter score: '))
except Exception as ex:
    print 'Score must be a number!'
    quit()

if score > 1: 
    print 'Score must be less than 1'
    quit()

grades = {
    'A': str(score) + '>= 0.9',
    'B': str(score) + '>= 0.8',
    'C': str(score) + '>= 0.7',
    'D': str(score) + '>= 0.6',
    'F': str(score) + '< 0.6',
}

for key in sorted(grades):
    if eval(grades[key]): 
        print key
        quit()

Solution

You're right to suspect that using eval stinks: it really does.

You can define the values of grades as tuples of functions and a parameter:

def greater_than_or_equal(score, x):
    return score >= x

def less_than(score, x):
    return score < x

grades = {
    'A': (greater_than_or_equal, 0.9),
    'B': (greater_than_or_equal, 0.8),
    'C': (greater_than_or_equal, 0.7),
    'D': (greater_than_or_equal, 0.6),
    'F': (less_than, 0.6),
}


Now you can write a get_grade function that returns the correct grade using the evaluator and a parameter:

def get_grade(score):
    for key in sorted(grades):
        evaluator, param = grades[key]
        if evaluator(score, param):
            return key


I converted the code that just prints the key and quits to a function,
so that you can verify the correct behavior using assertions:

assert 'F' == get_grade(0.1)
assert 'F' == get_grade(0.5)
assert 'D' == get_grade(0.6)
assert 'D' == get_grade(0.61)
assert 'C' == get_grade(0.7)
assert 'B' == get_grade(0.8)
assert 'A' == get_grade(0.9)
assert 'A' == get_grade(0.91)
assert 'A' == get_grade(1)


Actually, using a dictionary for grades doesn't make a lot of sense.
It could be just a list of tuples in the correct order so that you can even skip the sorting step:

grade_evaluators = (
    (greater_than_or_equal, 0.9, 'A'),
    (greater_than_or_equal, 0.8, 'B'),
    (greater_than_or_equal, 0.7, 'C'),
    (greater_than_or_equal, 0.6, 'D'),
    (less_than, 0.6, 'F'),
)

def get_grade(score):
    for evaluator, param, grade in grade_evaluators:
        if evaluator(score, param):
            return grade

Code Snippets

def greater_than_or_equal(score, x):
    return score >= x

def less_than(score, x):
    return score < x

grades = {
    'A': (greater_than_or_equal, 0.9),
    'B': (greater_than_or_equal, 0.8),
    'C': (greater_than_or_equal, 0.7),
    'D': (greater_than_or_equal, 0.6),
    'F': (less_than, 0.6),
}
def get_grade(score):
    for key in sorted(grades):
        evaluator, param = grades[key]
        if evaluator(score, param):
            return key
assert 'F' == get_grade(0.1)
assert 'F' == get_grade(0.5)
assert 'D' == get_grade(0.6)
assert 'D' == get_grade(0.61)
assert 'C' == get_grade(0.7)
assert 'B' == get_grade(0.8)
assert 'A' == get_grade(0.9)
assert 'A' == get_grade(0.91)
assert 'A' == get_grade(1)
grade_evaluators = (
    (greater_than_or_equal, 0.9, 'A'),
    (greater_than_or_equal, 0.8, 'B'),
    (greater_than_or_equal, 0.7, 'C'),
    (greater_than_or_equal, 0.6, 'D'),
    (less_than, 0.6, 'F'),
)

def get_grade(score):
    for evaluator, param, grade in grade_evaluators:
        if evaluator(score, param):
            return grade

Context

StackExchange Code Review Q#82243, answer score: 4

Revisions (0)

No revisions yet.