FAQ: Learn Python: Classes - Review

I don’t understand, if Python reads code line by line, top to bottom, then how can we check that the type of grade is that of the Grade class before we’ve defined the Grade class?

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) 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 pieter.add_grade(Grade(100))
1 Like

This might need a little more information as you don’t seem to be checking anything at any point in this code but the short answer is that you don’t. Typically you’d have function and class definitions at the top of your script (or more likely still in another module that you import) so they’d be defined before you attempt to use them.

See lines 7 - “if type(grade) == Grade” - and 14, where the Grade class is defined.

You can use names that yet to be defined in the body of the function since they only really need to be found at the time when you call it. Since you never actually call this function in the given example then it never actually has to look for Grade.

A rather blunt example-

def func():
    print(x)

x = 3
func()

If you tried to call it before Grade was defined in an accessible scope then it would throw a Name Error.

1 Like

Here is my solution.

class Student:
  def __init__(self, name, year):
    self.name = name
    self.year = year
    self.grades = []
    self.attendance = {}
    
  
  def get_average(self):
    grade_sum = 0
    count = 0
    for i in self.grades:
      count += 1
      grade_sum += i
    return grade_sum / count

  
  def add_grade(self, grade):
    if type(grade) is int:
      self.grades.append(grade)

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

  def is_passing(self, score):
    if score >= self.minimum_passing:
      return True
    else:
      return False 


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

pieter.add_grade(100)
pieter.add_grade(80)
pieter.add_grade(70)
pieter.add_grade(89)

student_score = Grade(0)
print(student_score.is_passing(89))
print(pieter.grades)
print(pieter.get_average())

pieter.attendance['01/01/22'] = 'Present'
pieter.attendance['01/02/22'] = 'Absent'
pieter.attendance['01/03/22'] = 'Present'

print(pieter.attendance)

I did have trouble with this piece:

def add_grade(self, grade):
    if type(grade) is int:
      self.grades.append(grade)

the assignment ask:
" Add an .add_grade() method to Student that takes a parameter, grade .

.add_grade() should verify that grade is of type Grade and if so, add it to the Student ‘s .grades .

If grade isn’t an instance of Grade then .add_grade() should do nothing."

this made my original code:

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

but then it caused problems with adding grades and finding averages so I changed it to what it is in my code solution. Curious if there was something I was missing. I think my code gets the right answers but not exactly the way the assignment wanted me to. I figure if the grade is just an int my code is fine, but I dont think it would have passed had I started with it going through the check points. I changed my code when I was doing the extra questions at the end.

Remember that Grade has a 'score' attribute that we need to poll when computing the average.

Hello,

In question 7, why do we need to include Grade in pieter.add_grade(Grade(100)) ? Why can’t we just write pieter.add_grade(100) ?

Thank you!

100 is not a Grade object. In order for our classes to work together we need to keep them as objects that can have their own methods.

1 Like

https://www.codecademy.com/workspaces/61f406114ad323964b6793a8

In the project challenge: “Create a game using classes and objects”,
i’m calling .approach.child method, the methods switches .want_to_play parameter to True. I don’t know how to pass the Child instance as child_1 into a specific Activity. ideally i want .approach_child or any other method of my own to pass it. If not possible, at least __repr__ to be able to pass it.
Not only pass the parameter, but also to actually call a specific instance of Activity.

Like most people here I definitely had a hard time with this exercise and especially the bonus practice questions at the end.
So after finally figuring out how to get_average I played around a little bit with functions which we learned in another lesson. My thinking was, it was time consuming to manually run individual “grade assignments” through classes and adding them to a student, get average, etc. So I defined some functions that could take in a list of grades and use the classes we created in the lessons.

Let me know what you think (feedback appreciated). Here is the link to the full code: https://www.codecademy.com/workspaces/62a839c8b0fe32e4008f1008

class Student: def __init__(self, name, year): self.name = name self.year = year self.grades = [] def add_grade(self, grade): if type(grade) is Grade: self.grades.append(grade) def get_average(self): return sum([x.score for x in self.grades])/len(self.grades) class Grade: minimum_passing = 65 def __init__(self, score): self.score = score def is_passing(self): if self.score >= Grade.minimum_passing: return "is a passing Grade" else: return "is not a passing Grade" roger = Student("Roger van der Weyden", 10) sandro = Student("Sandro Botticelli", 12) pieter = Student("Pieter Bruegel the Elder", 8) # Below are varying lists of assignments for testing list_of_assignments1 = [100, 0, 50] list_of_assignments2 = [90, 80, 55] list_of_assignments3 = [65, 64, 65, 68] # Here is the function to run which takes a student name and a list name from one of the lists above def add_grades_for_student(name, list_of_assignments): for i in list_of_assignments: temp_grade = Grade(i) name.add_grade(temp_grade) # Here you can get the average grade of the student and it will print if they are passing def get_student_average(name): average_grade = name.get_average() print("Average grade is: " + str(name.get_average())) check_for_passing = Grade(average_grade) status_of_passing = Grade.is_passing(check_for_passing) print(status_of_passing) # Here is where you call the functions add_grades_for_student(sandro, list_of_assignments1) get_student_average(sandro)

Some of the Forum posts definitely helped me understand classes a lot better and helped me remember details about classes that I quickly forgot after reading them.

Why doesn’t this line in the Student class throw a name error?

if type(grade) == Grade:

Grade hasn’t been defined yet so not sure what’s going on here

Edit: Actually it does throw an error when I tested it with some dummy code

s = Student(‘sam’, 43)
s.add_grade(5)

But the task still completed when I did the former. Weird

How do I use is_passing method in Grade using the numbers that are the grades defined in the instances of the Student class?

I can call it in Students class and use it to determine if the average student grade is passing or not. First I define it as a Grade instance then call the is_passing method from Grade. Is there a better way to use the is_passing method?

def get_average(self):
x = sum(self.get_grades())/len(self.grades)
y = Grade(x)
y_ = y.is_passing()
return “this is x: {x} and this is y: {y_}”.format(x=x, y_=y_)

class Grade:
minimum_passing = 65
def init(self,score):
self.score = score
def is_passing(self):
if self.score > self.minimum_passing:
return “PASS”
else:
return “FAIL”

I struggled with the following parts of this review exercise and was hoping to get some feedback.

Specifically:

  1. Computing the average grade and displaying it was hard. I had wanted to use the mean() function like this:
return mean(grade.score for grade in self.get_grades())

But that didn’t work. I now used a workaround that calculates the total and divides it by the # of grades. But it seems inefficient and verbose. How can I do this more simply?

  1. What would be the most efficient way to insert multiple grades at once? Is that creating a list of grades within the get_grade() method and looping over them to append them to the self.grades list? How should the input parameters for the function look if you don’t want to pre-define the amount of grades you’ll be adding?

  2. Do you have to add “self” as the first parameter in every function within a class (even ones other than init and repr)?

  3. How do I print a list of grades? Printing

student.grades

Unfortunately returns a hash string, like:

  Grades = [<__main__.Grade object at 0x7f1884912668>],

Is there any way to only extract the integer values?

You can find my total code below. Thank you!

from statistics import mean

# Create class
class Student:

  # Initiate with constructor variables
  def __init__(self, name, year):
    self.name = name
    self.year = year
    self.grades = []
    self.attendance = {}

  def __repr__(self):
    return self.name
  
  def add_grade(self, grade): # Why do I have to specify self here, if this isn't a constructor?
    if type(grade) is Grade:
      self.grades.append(grade)

  # Get average grade if student has reported grades.
  def get_average(self):
    if len(self.grades) > 0:
      total = 0
      for grade in self.grades:
        total += grade.score
      average = total / len(self.grades)
      print(f'{self.name} average grade is', average)
      return average
    else:
      print('No grades added yet.')

class Grade:
  def __init__(self, score):
    self.score = score
  
  def is_passing(self, score):
    minimum_passing = 65  # Had to move this to be inside the function.
    if score >= minimum_passing:
      return True
    else:
      return False

# Define instance variables for students.
roger = Student("Roger van der Weyden", 10)
sandro = Student("Sandro Botticelli", 12)
pieter = Student("Pieter Bruegel the Elder", 8)

# Add grades. Note I did not have to specify an argument for the 'self' parameter in either class method.
roger.add_grade(Grade(25))
sandro.add_grade(Grade(75))
pieter.add_grade(Grade(100))

# Add attendance
roger.attendance['27/04/2023'] = True
sandro.attendance['27/04/2023'] = True
pieter.attendance['27/04/2023'] = False

# Check if Pieter is passing.
print(Grade.is_passing(pieter, 100))

# Check avg grade
pieter.get_average()

# Print all information
students = [roger, sandro, pieter]
for student in students:
  print(f"""
  Name = {student.name},
  Year = {student.year},
  Avg grade = {student.get_average()},
  Grades = {student.grades},
  Attendance = {student.attendance}
  """)

The Grade.is_passing() method applies to a particular grade, e.g. Grade(100). To print it, I believe you would need to iterate through each Grade.score and call the Grade.is_passing() function with the Grade.score as the input.

How can the if statement in the add_grade compare anything to the Grade class? isnt that something outside of the class it’s in and therefore something it cannot see?

class Student: def __init__(self, name, year): self.name = name self.year = year self.grades = [] def add_grade(self, grade): if type(grade) is Grade: self.grades.append(grade) class Grade: minimum_passing = 65 def __init__(self, score): self.score = score

It can see it because it has an instance of it accessible in grades. The attribute we are looking for is, score.