2

I want to make the class score in python that takes each subject's score and return its average and sum. I know if I receive 4 arguments than denominator of average should be 4, but how I can make it to fixed codes that changed by number of inputs?, not just count numbers and type 4. I tried Len(self) or using for loop to count but Len makes error and for loop isn't that easy.

class score:
    def __init__(self, language, math, english, science):
        self.language = language
        self.math = math
        self.english = english
        self.science = science
        
    ...

    def sum_scores(self):
        result = self.language + self.math + self.english + self.science
        return result
    
    def average(self):
        average = self.sum_scores()/4 # Here is the problem!
        return average

This is my first question on stack. so sorry for my poor English and stupid questions.

고모드
  • 23
  • 5
  • Is it required that you have a fixed number of named arguments? Or can you have a variable number of arguments? Is it required that you know the subject for each score, or is it OK to just know the score? – SethMMorton Apr 27 '21 at 16:29
  • 1
    I don't understand the question. Since `__init__` has four **mandatory** arguments (not counting `self`) the correct denominator will always be `4`. – timgeb Apr 27 '21 at 16:30
  • 1
    @timgeb That's still fragile in the face of adding a new score, as you have to remember to update the use of `4` (or a constant) in addition to actually adding the new score. – chepner Apr 27 '21 at 16:32
  • @chepner fair point – timgeb Apr 27 '21 at 16:32
  • oh! the code needed because if I took 5 or more inputs than the denominator should be change. so I don't want to change the denominator each time by entering the number when I receive more inputs! – 고모드 Apr 27 '21 at 16:33

4 Answers4

2

Don't use 4 separate attributes in the first place. Use a dict to store the attributes; then you can query the size of the dict.

class score:
    def __init__(self, language, math, english, science):
        self.scores = {'language': language, 'math': math, 'english': english, 'science': science}
        
    ...

    def sum_scores(self):
        return sum(self.scores.values())
        
    
    def average(self):
        return self.sum_scores() / len(self.scores)

If you still want attributes for each individual score, use properties:

class score:
    def __init__(self, language, math, english, science):
        self.scores = {'language': language, 'math': math, 'english': english, 'science': science}
        

    @property
    def language(self):
        return self.scores['language']

    # etc.


    def sum_scores(self):
        return sum(self.scores.values())
        
    
    def average(self):
        return self.sum_scores() / len(self.scores)
chepner
  • 497,756
  • 71
  • 530
  • 681
  • 1
    @고모드 You should click the green check mark of the answer with the solution that you found most helpful. – SethMMorton Apr 27 '21 at 16:39
2

For your case, you could just note that the quantity is 4

However, you may want to use **kwargs as an argument (you can use any name, this is just convention; ** assigns all the keyword args not specifically named to a dict) instead

SUBJECTS = ("math", "science" ... )

class MyClass():
    def __init__(self, **kwargs):  # also consider *args
        self.subjects = {k: v for k, v in kwargs.items() if k in SUBJECTS}

    def sum_scores(self):
        return sum(self.subjects.values())
    
    def average(self):
        return self.sum_scores() / len(self.subjects)
>>> c = MyClass(math=10, science=8)
>>> c.sum_scores()
18
>>> c.average()
9.0

You may also want to consider reporting on arguments that are not used to help users find usage errors

ti7
  • 16,375
  • 6
  • 40
  • 68
  • Your code is advocating some tight coupling between global objects and the class. It might be better to make your global `subjects` a class attribute. – SethMMorton Apr 27 '21 at 16:36
  • 1
    That's fair - it's the [fastest gun in the west](https://meta.stackexchange.com/questions/9731/fastest-gun-in-the-west-problem) around here though! I think a good trade-off is the capitalized, immutable global; class attributes can be difficult to reason about when mixing inheritance (though obviously with benefits!) – ti7 Apr 27 '21 at 16:41
2

Assuming that it is OK that you don't know the subjects for which the scores apply, you can just use unnamed positional arguments

class score:
    def __init__(self, *args):
        self.scores = args
        
    ...

    def sum_scores(self):
        return sum(self.scores)
    
    def average(self):
        return self.sum_scores() / len(self.scores)
SethMMorton
  • 45,752
  • 12
  • 65
  • 86
  • So your code is not applying scores to subjects, just receive all the scores(whatever the subjects are) and calculate the average, am I right? – 고모드 Apr 28 '21 at 00:17
  • 1
    Correct. The code in the question did not appear to use the subject names at all so I formulated this answer based on the assumption that the subject names were just placeholders. – SethMMorton Apr 28 '21 at 00:46
1

You can use keyword arguments in your init function:

class Score:
     def __init__(self, **scores):
         self.classes = []
         for _class, score in scores.items():
             setattr(self, _class, score) # self.english, self.math, etc.
             self.classes.append(_class) # ["math", "english"]
     def sum_scores(self):
         sum = 0
         for i in self.classes:
              sum += getattr(self, i)
         return sum
     def average(self):
         return self.sum_scores() / len(self.classes)

By using **scores, you can dynamically iterate over all arguments passed into the function as a dictionary. Here, I use the setattr function to give the Score object a property of its class name, and its value is the corresponding value from the dictionary. That way, I can dynamically iterate over each class, whether 3 or 100 classes are provided as input.

score1 = Score(math=67, english=98, art=76)

print(score1.sum_scores())
print(score1.average())
>>> 241
>>> 80.3333333333
Dharman
  • 30,962
  • 25
  • 85
  • 135
Soggy
  • 48
  • 4
  • **I'm very impressed!!** your code doesn't need to designate all the method like ' def math..(self)'. 'setattr' and 'getattr' is very powerful tool, I think!! I am really concerned about how to return each subject's score whatever I receive, it solved both of my problems!! – 고모드 Apr 28 '21 at 02:19