1
\$\begingroup\$

I am a new coder and have seen people on Stack discuss why eval use is not recommended. In the following code, we have been given a set of dictionaries with the marks of the students, we take the student name as an input and provide the output as per the formula given (the formula for the calculation of marks).

 # 1. Jack's dictionary
Jack = { "name":"Jack",
         "assignment" : [80, 50, 40, 20],
         "test" : [75, 75],
         "lab" : [78.20, 77.20]
       }
        
# 2. James's dictionary
james = { "name":"James",
          "assignment" : [82, 56, 44, 30],
          "test" : [80, 80],
          "lab" : [67.90, 78.72]
        }
 
# 3. Dylan's dictionary
dylan = { "name" : "Dylan",
          "assignment" : [77, 82, 23, 39],
          "test" : [78, 77],
          "lab" : [80, 80]
        }
         
# 4. Jessica's dictionary
jess = { "name" : "Jessica",
         "assignment" : [67, 55, 77, 21],
         "test" : [40, 50],
         "lab" : [69, 44.56]
       }
        
# 5. Tom's dictionary
tom = { "name" : "Tom",
        "assignment" : [29, 89, 60, 56],
        "test" : [65, 56],
        "lab" : [50, 40.6]
      }
name = eval(input())
marks = (0.1*(sum(name['assignment'])/len(name['assignment']))) + (0.7*(sum(name['test'])/len(name['test']))) + (0.2*(sum(name['lab'])/len(name['lab'])))
asci = 65 
print('Average marks of {} is :'.format(name['name']),marks)
def GradeAssign(a,marks,asci):
  if a >= 60: 
   if marks >= a:
      print('Letter Grade of {} is :'.format(name['name']),chr(asci))
   else : 
      a -= 10
      asci += 1
      GradeAssign(a,marks,asci)   
  else :
     print('Letter Grade of {} is :'.format(name['name']),'E')
GradeAssign(90,marks,asci)  

Now I can't seem to figure out a way to not use eval in the following line:

name = eval(input())

 
\$\endgroup\$
6
  • 3
    \$\begingroup\$ Why not make a dictionary where the keys are names and values are score dictionaries? Then you can just use name = input(), and use that to access the values in the dictionary. \$\endgroup\$
    – sg7610
    Commented Dec 16, 2021 at 6:03
  • \$\begingroup\$ I can't change the dictionary as it is provided in the question \$\endgroup\$ Commented Dec 16, 2021 at 11:05
  • 2
    \$\begingroup\$ If you can't change the dictionary that is provided to you. Just make a new one out of it. \$\endgroup\$
    – sg7610
    Commented Dec 16, 2021 at 11:12
  • \$\begingroup\$ If this is homework please add the homework tab. That would clarify the restrictions on the code. \$\endgroup\$
    – pacmaninbw
    Commented Dec 16, 2021 at 13:01
  • \$\begingroup\$ @pacmaninbw I'll do that from the next time, I am fairly new to stack in general so thanks for the help. \$\endgroup\$ Commented Dec 17, 2021 at 6:00

1 Answer 1

3
\$\begingroup\$

Start with a solid foundation. Even simple scripts like this can benefit by following a simple rule: put all algorithmic code inside of functions. At the top level, you can perform imports or define constants, functions, or classes. Everything else must be in functions. For this program, we could start with the following sketch:

jack = {...}
james = {...}
dylan = {...}
jess = {...}
tom = {...}

def main():
    student = get_student()
    score = compute_score(student)
    letter = compute_grade(score)
    print(score, letter)

def get_student():
    return jack

def compute_score(student):
    return 100

def compute_grade(score):
    return 'A'

if __name__ == '__main__':
    main()

Take advantage of data structures. You have 5 constants in the form of student dicts. And you have a name that will be entered by the program user (e.g., 'jack'). You don't need exotic techniques like eval() to transform that user-entered string into a Python variable. Rather, you need a dict mapping each student name to its corresponding dict of information about the student. This simple function will do the trick:

def collect_students():
    return {
        d['name'].lower() : d
        for d in (jack, james, dylan, jess, tom)
    }

Validate user input. Once we have that utility function, we can implement the behavior to get user input and return the corresponding student dict. Code to collect user input should normally be written with an awareness that people make mistakes. A while True loop is often the most flexible mechanism for these situations: get the input, validate it, and return if OK:

def get_student():
    students = collect_students()
    while True:
        name = input('Enter student name: ')
        try:
            return students[name.lower()]
        except KeyError:
            pass

Use data structures to simplify algorithms. Your code to compute the student's overall score is repetitive (it computes 3 different means) and hard to read (a long, dense line of code). However, if we define a simple data structure -- in this case, a dict mapping each type of coursework to its weight in the overall score -- we can compute the overall score more understandably:

def compute_score(student):
    weights = {'assignment': 0.1, 'test': 0.7, 'lab': 0.2}
    return sum(
        w * mean(student[k])
        for k, w in weights.items()
    )

def mean(vals):
    # Better: raise exception if vals is empty.
    # Even better: use statistics.mean().
    return sum(vals) / len(vals)

Use data structures to simplify algorithms -- yet again. Your code to compute the letter grade is algorithmically complex (relying on recursion) and opaque (with cryptic variable names like asci and a). None of that is needed if you define a dict mapping each minimum-score to its corresponding letter grade. (Note that this relies on the insertion-ordering property of dicts in modern Python.)

def compute_grade(score):
    grades = {90: 'A', 80: 'B', 70: 'C', 60: 'D'}
    for min_score, letter in grades.items():
        if score >= min_score:
            return letter
    return 'E'
\$\endgroup\$
1
  • 1
    \$\begingroup\$ I really appreciate your detailed answer, I am working on making my code more streamlined and this was really helpful. \$\endgroup\$ Commented Dec 17, 2021 at 6:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.