4
\$\begingroup\$

I wrote a "gradebook" program. This takes in grades for various assignments. The assignments types are weighted differently and final grade is computed at the end. The code works but I can't help but think I am being ham handed in my approach. Should I be using a dictionary instead of storing things like hw_final, lab_final, and exam_final as global variables?

hw_tally = 0
hw_weight_count = 0
hw_final = 0
lab_tally = 0
lab_weight_count = 0
lab_final = 0
exam_tally = 0
exam_weight_count = 0
exam_final = 0


def assignment(kind, grade, weight=1):
    global hw_tally, hw_weight_count, hw_final, lab_tally, lab_weight_count, lab_final, exam_tally, exam_weight_count, exam_final
    if kind == 'hw':
        hw_weight_count += weight
        hw_temporary = (grade * weight)     
        hw_tally += hw_temporary
        hw_final = hw_tally/hw_weight_count
    elif kind == 'lab':
        lab_weight_count += weight
        lab_temporary = (grade * weight)     
        lab_tally += lab_temporary
        lab_final = lab_tally/lab_weight_count
    elif kind == 'exam':
        exam_weight_count += weight
        exam_temporary = (grade * weight)   
        exam_tally += exam_temporary
        exam_final = exam_tally/exam_weight_count


assignment('hw', 81)
assignment('exam', 90, 4)
assignment('hw', 100, 2)
assignment('lab', 84, 3)
assignment('lab', 90, 2)
assignment('exam', 78)
assignment('exam', 85)
assignment('lab', 88)
assignment('exam', 75)

hw_weight_percent = 0.4
lab_weight_percent = 0.1
exam_weight_percent = 0.5

final_grade = ((hw_final * hw_weight_percent) + (lab_final * 
lab_weight_percent) + (exam_final * exam_weight_percent))
print ("Final grade = ", final_grade)
\$\endgroup\$
  • 1
    \$\begingroup\$ @depperm Comments are for seeking clarification to the question, and may be deleted. Please put all suggestions for in answers, even if they are brief. \$\endgroup\$ – 200_success May 1 '18 at 16:25
1
\$\begingroup\$

Yes it would be better to use a dictionary, that would reduce the amount of global variables. Also you do not need to store and calculated the ..._final all the times, you can calculate it only when you need need it. The (nested) dictionary to store the grades could be:

scores = {
    "hw"   : { "tally" : 0, "weight_count" : 0} ,
    "lab"  : { "tally" : 0, "weight_count" : 0} ,
    "exam" : { "tally" : 0, "weight_count" : 0} ,
}

Then the assignment function can be made shorter, without calculating the final at this moment:

def assignment(kind, grade, weight=1):
    global scores
    scores[kind]["weight_count"] += weight
    scores[kind]["tally"] += grade * weight

We could write another function to calculate the final of a kind:

def final_of_kind(kind):
    global scores
    return scores[kind]["tally"] / scores[kind]["weight_count"]

And the rest of the program is almost the same:

assignment('hw', 81)
assignment('exam', 90, 4)
assignment('hw', 100, 2)
assignment('lab', 84, 3)
assignment('lab', 90, 2)
assignment('exam', 78)
assignment('exam', 85)
assignment('lab', 88)
assignment('exam', 75)

hw_weight_percent = 0.4
lab_weight_percent = 0.1
exam_weight_percent = 0.5

final_grade = (  (final_of_kind("hw")   * hw_weight_percent)
               + (final_of_kind("lab")  * lab_weight_percent)
               + (final_of_kind("exam") * exam_weight_percent))

print ("Final grade = ", final_grade)

Note: the alignment in the assignment of final_grade is frowned up on sometimes, because it is not according to PEP8, but I use it for readability.

\$\endgroup\$
  • \$\begingroup\$ thanks Jan. I like your solution more it is easier for me to follow. The above solution is beyond my level of experience \$\endgroup\$ – Erich Purpur May 2 '18 at 15:31
3
\$\begingroup\$
  1. Split assignment into three functions. homework, exam and labwork. Take:

    def homework(grade, weight=1):
        global hw_tally, hw_weight_count, hw_final
        hw_weight_count += weight
        hw_temporary = (grade * weight)     
        hw_tally += hw_temporary
        hw_final = hw_tally/hw_weight_count
    
  2. Change homework into a class.

    class Scores:
        def __init__(self):
            self.tally = 0
            self.weight_count = 0
    
        @property
        def final(self):
            return self.tally / self.weight_count
    
        def grade(grade, weight=1):
            self.weight_count += weight
            self.tally += grade * weight
    

    Now you can use the above class to total all the separate things. And then perform your total on Scores.final.

  3. Group the exam types into another class, say StudentScores.

  4. Expand Scores to also take the percentage. And make final return the percentage.
  5. Move your final calculation into StudentScores.

This can make:

class Scores:
    def __init__(self, percentage=1):
        self.tally = 0
        self.weight_count = 0
        self.percentage = percentage

    @property
    def final(self):
        return self.tally / self.weight_count * self.percentage

    def grade(self, grade, weight=1):
        self.weight_count += weight
        self.tally += grade * weight


class StudentScores:
    def __init__(self, scores):
        total = sum(scores.values())
        self._scores = {
            key: Scores(score / total)
            for key, score in scores.items()
        }

    def __getattr__(self, name):
        return self._scores[name]

    @property
    def grade(self):
        return sum(scores.final for scores in self._scores.values())


student = StudentScores({
    'homework': 0.4,
    'labwork': 0.1,
    'exam': 0.5
})

student.homework.grade(81)
student.exam.grade(90, 4)
student.homework.grade(100, 2)
student.labwork.grade(84, 3)
student.labwork.grade(90, 2)
student.exam.grade(78)
student.exam.grade(85)
student.labwork.grade(88)
student.exam.grade(75)

print("Final grade = ", student.grade)

This uses a lot of classes, and isn't really the best way if you're taking data from a file. And so if you store your results as a JSON file, and read it into Python however you want. Then you can default the weights into the JSON data. And then perform the rest of the code in a couple of comprehensions, wrapped in a sum:

results = {
    'homework': [
        0.4,
        [
            [81],
            [100, 2]
        ]
    ],
    'labwork': [
        0.1,
        [
            [84, 3],
            [90, 2],
            [88]
        ]
    ],
    'exam': [
        0.5,
        [
            [90, 4],
            [78],
            [85],
            [75]
        ]
    ]
}

def default_weights(results):
    return [
        [percentage, [(score + [1])[:2] for score in scores]]
        for percentage, scores in results.values()
    ]

def grade(results):
    return sum(
        percentage * sum(s*w for s, w in scores) / sum(w for _, w in scores)
        for percentage, scores in results
    )

print("Final grade = ", grade(default_weights(results)))
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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