Python Classes Review - Further Practice Discussion

sample code:

Output for the print statements is shown below :

print(roger)
print(sandro)
print(pieter)
print("=====================")
print(f"Was {roger.name} in attendance on '24 Jan 2020?' : {roger.get_attendance('01/22/2020')}")

ccClassesReview

3 Likes

Hi, I am struggleling with an error who tells me it missing a postionnal argument “self”, on the condition “if Grade.is _passing():”
After some research, it looks like it is because I should create an instance of my class before to call a method. But my objects already exist (pieter = Student(“Pieter Bruegel the Elder”, 8) and pieter_grade = Grade([85, 77, 100]) ).
Someone to show me the way please?

class Student:
    def __init__(self, name, year):
        self.name = name
        self.year = year
        self.grades = []

    def __repr__(self):
        if Grade.is_passing():
            return "Student: " + str(self.name) +", " + str(self.year) + " years, grades is " + str(self.grades[0]) + ", student is passing"
        else:
            return "Student: " + str(self.name) +", " + str(self.year) + " years, grades is " + str(self.grades[0]) + ", student is not passing"


    def add_grade(self, grade):
        if isinstance(grade, Grade):
          self.grades.append(grade)


####################################################
roger = Student("Roger van der Weyden", 10)
sandro = Student("Sandro Botticelli", 12)
pieter = Student("Pieter Bruegel the Elder", 8)
###################################################


class Grade:
    minimum_passing = 65

    def __init__(self, score):
        self.score = score
        self.total = 0
        for num in score:
            self.total += num

    def __repr__(self):
        average = self.total / len(self.score)
        self.result = str(round(average, 2))
        return "Average score is: " + str(self.result)

    def is_passing(self):
        if float(self.result) >= float(self.minimum_passing):
            #return print("passing")  #return None in addition on the print, why?
            return True
        else:
            #return print("not passing") #return None in addition on the print, why?
            return False



####################################################
pieter_grade = Grade([85, 77, 100])
pieter.add_grade(pieter_grade)
print(pieter)
####################################################

We can see where that is going to be a problem.

grades is a list of Grade objects, each with a score attribute and the is_passing method of their parent class. An individual grade could be assessed but one doubts there is only a single grade throughout the term so an average of the scores would be a more likely desired outcome. We can test the average to see its is_passing status.

One could write a get_average() method in the Student class.

def get_average(self):
    return sum([x.score for x in self.grades]) / len(self.grades)

Now we can compute any average at any point in the term to monitor progress.

f"{Grade(get_average()).score.is_passing()}"  # transient instance

The Grade.is_passing() method can be simplified:

return self.score >= self.minimum_passing

In my view, the Grade object should be simple. A score, and an is_passing method. None of the other logic you have above. Let the Student instance have access to all utility methods except is_passing.

1 Like

Thank you @mtf for your support, I finally reorganised the structure.

I noticed if you removed the “.score” in [x.score for x in self.grades], it will print the memory adress of the Grade object, so I understand this is like the “self” of a method, it refers to the object created.

Also, maybe I didn’t catch what you try to told me, but

def get_average(self):
    return sum([x.score for x in self.grades]) / len(self.grades)

didn’t work, it try to add list together, I fixed it with:

def get_average(self):
        self.average = round(sum([y for x in self.grades for y in x.score]) / len([y for x in self.grades for y in x.score]),2)

thanks for that you forced me to work on list comprehension!

I finally put my is_passing() method in the student class, because I could not figure out to access to it when it was in the Grade class.

My final code is that:

class Student:
    def __init__(self, name, year):
        self.name = name
        self.year = year
        self.grades = []
        self.average = 0
        self.passing = ""

    def __repr__(self):
        return str(self.name) + ", " + str(self.year) + " years" + ", average is: " + str(self.average) + ", " + self.passing

    def add_grade(self, grade):
        if isinstance(grade, Grade):
          self.grades.append(grade)
        return self.grades

    def get_average(self):
        self.average = round(sum([y for x in self.grades for y in x.score]) / len([y for x in self.grades for y in x.score]),2)


    def is_passing(self):
        if self.average >= Grade.minimum_passing:
            self.passing = "passing"
        else:
            self.passing = "not passing"




class Grade:
    minimum_passing = 65

    def __init__(self, score):
        self.score = score




####################################################
pieter = Student("Pieter Bruegel the Elder", 8)
pieter_grade = Grade([10, 5, 2])
pieter.add_grade(pieter_grade)
pieter.get_average()
pieter.is_passing()
print(pieter)
####################################################
1 Like

Given that all the values in the grades array are Grade objects, each with a score attribute, when we iterate over the objects individually then our iteration variable fills in for self. Each x is a self as in owner object, hence, x.score.

There are two objects, the instance, which is represented by self and the Grade object, which we give a name within the comprehension.

[x.score for x in self.grades]

should result in a list of grade scores (numbers) which we can add up using the sum() function. Not sure why it wouldn’t work for you.

Our classes look quite a bit different. Recall that we mentioned leaving the is_passing method in the Grade class. We shouldn’t need the two attributes, average or passing since those values are dynamically determined on any one instance of the Student class.

At any length, if you have it running, then that’s a plus. When it comes time to review or get in more practice, loop back around to this and give it another go to see if it can be simplified.

3 Likes

A post was split to a new topic: Python Classes Review - Further Practice Discussion

Edit 2: I added a line of code to return a different message for a student with perfect attendance.

Edit 1: I figured out how to do what I was wanting which was to keep count of the number of absences. I converted the boolean values to integers then counted the number of ‘False’ values.

I figured out how to manipulate the different data the way I want to, I think. However, can someone please help me figure out how get my absence counter to work? It works for Pieter because he only has 1 absence, but it also says that Roger has 1 absence when he has 2. I’m sure it’s simple, but my brain is FRIED! I will edit later after I’ve worked on it some more. I think I need to review the options I have for working with the list of values in a dictionary. And I might have just answered my own question…be back soon!

class Student:
  def __init__(self, name, year):
    self.name = name
    self.year = year
    self.grades = []
    self.attendance = {}
    
  def add_grade(self, grade):
    if type(grade) == Grade:
      self.grades.append(grade.score)
    return grade
  
  def get_avg(self):
    total_grades = 0
    minimum_passing = 65
    for score in self.grades:
     total_grades += score
     average = total_grades/len(self.grades)
    if average >= minimum_passing:
      return "My grade average is: {:1f}\nI am passing! :)".format(average)
    else:
      return "My grade average is: {:1f}\nI am failing! :(".format(average)

  def add_attendance(self, date, record):
    self.attendance[date] = record
    
def get_attendance(self):
    attendance = []
    for x in self.attendance.values():  
        attendance.append(int(x))
    for x in attendance:
      if sum(attendance) == len(self.attendance):
        return "I have perfect attendance!"
      else:
        return "I have {} absence/s recorded.".format(attendance.count(0))
  
  def __repr__(self):
    return "My name is {} and I am in year {}.\nThese are my grades: {}\n{}\nAttendance Record: {}\n{}\n".format(self.name, self.year, self.grades, self.get_avg(), self.attendance, self.get_attendance())

class Grade:
  minimum_passing = 65
  def __init__(self, score):
    self.score = score

  def passing_score(grade):
    minimum_passing = 65
    if grade >= minimum_passing:
      return "This is a passing score!"
    else:
      return "You are failing!"
          
roger = Student("Roger van der Weyden", 10)
sandro = Student("Sandro Botticelli", 12)    
pieter = Student("Pieter Bruegel the Elder", 8)

pieter.add_grade(Grade(100))
pieter.add_grade(Grade(75))
pieter.add_grade(Grade(85))

sandro_grades = [90, 80, 95, 75, 65, 50, 45, 40, 35]
roger_grades = [95, 66, 78, 89, 98]

for x in sandro_grades:
    sandro.add_grade(Grade(x))

for x in roger_grades:
    roger.add_grade(Grade(x))

pieter_attendance = [('2/6/2021', True), ('2/5/2021', False), ('2/7/2021', True)]
sandro_attendance = [('2/6/2021', True), ('2/5/2021', True), ('2/7/2021', True)]
roger_attendance = [('2/6/2021', False), ('2/5/2021', False), ('2/7/2021', True)]

for x in list(pieter_attendance):
    date = x[0]
    record = x[1]
    pieter.add_attendance(date, record)

for x in list(sandro_attendance):
    date = x[0]
    record = x[1]
    sandro.add_attendance(date, record)

for x in list(roger_attendance):
    date = x[0]
    record = x[1]
    roger.add_attendance(date, record)  
  
#print(pieter)
#print(sandro)
#print(roger)
3 Likes

This is my code but I don’t know why system annouced that it’s an error in my def attendance even I found it’s kind similar to you and I don’t know why. This is the error it is showed:

Traceback (most recent call last):
  File "script.py", line 46, in <module>
    pieter.attendance(date, status)
  File "script.py", line 18, in attendance
    self.attendance[date] = status
TypeError: 'method' object does not support item assignment
class Student:

  def __init__(self, name, year):

    self.name = name

    self.year = year

    self.grades = []

  

  def add_grade(self, grade):

    if type(grade) == Grade:

      self.grades.append(grade)

  def get_average(self):

    s = 0

    for student in self.grades:

      s += student.score

    return s / len(self.grades)

  def attendance(self, date, status):

    self.attendance[date] = status

#################################

class Grade:

  minimum_passing = 65

  def __init__(self, score):

    self.score = score

    self.passed = str(self.score >= self.minimum_passing)

  def is_passing(self, score):

    if self.score >= minimum_passing:

       return True

    else:

       return False

  

  def __repr__(self):

    return self.passed

######################################

roger = Student("Roger van der Weyden", 10)

sandro = Student("Sandro Botticelli", 12)

pieter = Student("Pieter Bruegel the Elder", 8)

pieter.add_grade(Grade(100))

pieter_attendance = [('2/6/2021', True)]

for i in list(pieter_attendance):

  date = i[0]

  status = i[1]

  pieter.attendance(date, status)

score = Grade(100)

print(score)

Take a look at the following method.

You’re trying to assign a key self.attendance with a value. However, because you didn’t instantiate self.attendance = {} at the beginning of your class, what happens is that it tries to assign the method itself, because it’s also named attendance, which doesn’t work. That’s where you get the TypeError: 'method' object does not support item assignment.

Thank you for your answering, I added the instantiate at the beginning and I fixed the first problem but there is still other error called:

Traceback (most recent call last):
  File "script.py", line 47, in <module>
    pieter.attendance(date, status)
TypeError: 'dict' object is not callable
class Student:

  def __init__(self, name, year):

    self.name = name

    self.year = year

    self.grades = []

    self.attendance = {}

  

  def add_grade(self, grade):

    if type(grade) == Grade:

      self.grades.append(grade)

  def get_average(self):

    s = 0

    for student in self.grades:

      s += student.score

    return s / len(self.grades)

  def attendance(self, date, status):

    self.attendance[date] = status

#################################

class Grade:

  minimum_passing = 65

  def __init__(self, score):

    self.score = score

    self.passed = str(self.score >= self.minimum_passing)

  def is_passing(self, score):

    if self.score >= minimum_passing:

       return True

    else:

       return False

  

  def __repr__(self):

    return self.passed

######################################

roger = Student("Roger van der Weyden", 10)

sandro = Student("Sandro Botticelli", 12)

pieter = Student("Pieter Bruegel the Elder", 8)

pieter.add_grade(Grade(100))

pieter_attendance = [('2/6/2021', True)]

for i in list(pieter_attendance):

  date = i[0]

  status = i[1]

  pieter.attendance(date, status)

score = Grade(100)

print(score)

the error says:

TypeError: 'dict' object is not callable

which means you try to call the dictionary as if it where a function. How do we add a key value pair to the dictionary?

1 Like

a quick question:

class Grade:
  minimum_passing = 65 # atribute
  def __init__(self, score):
    self.score = score #is it like saying set this class score to be the 
                      #score passed in?

self.score is the instance variable, which we can then access in other methods

score is the parameter of the init method

why does python has too many self? I’m kind of a beginner in c# and I keep thinking why python has too many self isn’t supposed to be easier ? OOP in c# seems to be less complicated…

Is it normal to think that? Maybe since I’m starting, and I’m not accustomed to the language

self is just a way of addressing object context. Unlike JavaScript, objects in Python do not have a fixed variable (this) so every method must have a formal parameter so it can see the scope above. What we call it is unimportant.

Witness:

>>> class foo():
	def __init__(self, a):
		self.a = a
	def __repr__(self):
		return str(dir(self))

	
>>> b = foo('a')
>>> print (b)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'a']
>>> 

And now,

>>> class foo():
	def __init__(this, a):
		this.a = a
	def __repr__(this):
		return str(dir(this))

	
>>> b = foo('a')
>>> print (b)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'a']
>>> 
2 Likes

I can’t figure out why my code is producing the following:

[<main.Grade object at 0x7fcd59914f28>, <main.Grade object at 0x7fcd59914f60>, <main.Grade object at 0x7fcd59914f98>]
<bound method Student.get_average of <main.Student object at 0x7fcd59914ef0>>

class Student: def __init__(self, name, year): self.name = name self.year = year self.grades = [] self.average = 0 self.total = 0 def add_grade(self, grade): if type(grade) == Grade: self.grades.append(grade) else: pass def get_average(self): for each in self.grades: self.total += each self.average = self.total / len(self.grades) return self.average class Grade: minimum_passing = 65 def __init__(self, score): self.score = score def is_passing(self): return self.score >= self.minimum_passing roger = Student("Roger van der Weyden", 10) sandro = Student("Sandro Botticelli", 12) pieter = Student("Pieter Bruegel the Elder", 8) pieter.add_grade(Grade(100)) pieter.add_grade(Grade(90)) pieter.add_grade(Grade(80)) print(pieter.grades) print(pieter.get_average)

Please help.

For starters, print(pieter.get_average). When we examine the class definition, get_average is a method, not an attribute that carries a value. Fix this, and return if more issues persist. We did spot another suspicious bit of code but will wait until this fix is in.

1 Like

Thank you for the tip. I fixed it and I changed:

self.grades.append(grade.score)

I realized that grade was being received by the method as an object and not an int. So the method was adding the objects to the list - which makes sense why get_average wasn’t working properly either.

2 Likes

yes, finally i can see it. if i tried adding a score, just as int not as class Grade, it wouldn’t work and it makes sense.

syntax is really complicated and not intuitively obvious though. i think i’ll start calling my objects whatever_obj, just so it’s immediately obvious for me, that when i pass it as an argument of a function of some other class, that it is indeed an object (with it’s own properties).

yes, finally i can see it. if i tried adding a score, just as int not as class Grade, it wouldn’t work and it makes sense.

syntax is really complicated and not intuitively obvious though. i think i’ll start calling my objects whatever_obj, just so it’s immediately obvious for me, that when i pass it as an argument of a function of some other class, that it is indeed an object (with it’s own properties).