Program to compute final grade for weighted assignments 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)
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

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.
Group the exam types into another class, say StudentScores.
Expand Scores to also take the percentage. And make final return the percentage.
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)))