Classes

Classes allow you to define how to package data with functions to create objects. An object is an instance of a class, which contains its own data, and its own copy of functions that can operate on that data.

You use classes to define objects that represent the concepts and things that your program will work with. For example, if your program managed exam results of students, then you may create one class that represents an Exam, and another that represents a Student.

In [ ]:
class Exam:
    def __init__(self, max_score=100):
        self._max_score = max_score
        self._actual_score = 0
        
    def percent(self):
        return 100.0 * self._actual_score / self._max_score
    
    def setResult(self, score):
        if (score < 0):
            self._actual_score = 0
        elif (score > self._max_score):
            self._actual_score = self._max_score
        else:
            self._actual_score = score
    
    def grade(self):
        if (self._actual_score == 0):
            return "U"
        elif (self.percent() > 90.0):
            return "A"
        elif (self.percent() > 80.0):
            return "B"
        elif (self.percent() > 70.0):
            return "C"
        else:
            return "F"
In [ ]:
class Student:
    def __init__(self):
        self._exams = {}
    
    def addExam(self, name, exam):
        self._exams[name] = exam
        
    def addResult(self, name, score):
        self._exams[name].setResult(score)
    
    def result(self, exam):
        return self._exams[exam].percent()
    
    def grade(self, exam):
        return self._exams[exam].grade()
    
    def grades(self):
        g = {}
        for exam in self._exams.keys():
            g[exam] = self.grade(exam)
        return g

We can now create a student, and give them a set of exams that they need to complete.

In [ ]:
s = Student()
In [ ]:
s.addExam( "maths", Exam(20) )
In [ ]:
s.addExam( "chemistry", Exam(75) )

At this point, the student has not completed any exams, so the grades are all 'U'

In [ ]:
s.grades()

However, we can now add the results...

In [ ]:
s.addResult("maths", 15)
In [ ]:
s.addResult("chemistry", 62)
In [ ]:
s.grades()

Programming with classes makes the code easier to read, as the code more closely represents the concepts that make up the program. For example, here we have a class that represents a full school of students.

In [ ]:
class School:
    def __init__(self):
        self._students = {}
        self._exams = []

    def addStudent(self, name):
        self._students[name] = Student()

    def addExam(self, exam, max_score):
        self._exams.append(exam)
        
        for key in self._students.keys():
            self._students[key].addExam(exam, Exam(max_score))
    
    def addResult(self, name, exam, score):
        self._students[name].addResult(exam, score)
        
    def grades(self):
        g = {}
        for name in self._students.keys():
            g[name] = self._students[name].grades()
        return g

We can now create a whole school of students and manage the exams and results for all of them with some reasonably readable code :-)

In [ ]:
school = School()
In [ ]:
school.addStudent("Charlie")
In [ ]:
school.addStudent("Matt")
In [ ]:
school.addStudent("James")
In [ ]:
school.addExam( "maths", 20 )
In [ ]:
school.addExam( "physics", 50 )
In [ ]:
school.addExam( "english literature", 30 )
In [ ]:
school.grades()

We can now add in the results of the exams, which have been returned to us by the exam markers...

In [ ]:
englit_results = { "Charlie" : 10, "Matt" : 25, "James" : 3 }
In [ ]:
phys_results = { "Matt" : 48, "James" : 3 }
In [ ]:
maths_results = { "James" : 20, "Matt" : 18, "Charlie" : 4 }

Indeed, we will do this by using a function...

In [ ]:
def add_results(school, exam, results):
    for student in results.keys():
        school.addResult(student, exam, results[student])
In [ ]:
add_results(school, "english literature", englit_results)
In [ ]:
add_results(school, "physics", phys_results)
In [ ]:
add_results(school, "maths", maths_results)
In [ ]:
school.grades()

Exercise

Exercise 1

Here is a copy of the Morse class from the last section. Modify this class to add in a decode function that converts Morse code back to english. Check that this class works by seeing if m.decode( m.encode(message) ) == message.lower().

In [ ]:
class Morse:
    def __init__(self):
        self._letter_to_morse = {'a':'.-', 'b':'-...', 'c':'-.-.', 'd':'-..', 'e':'.', 'f':'..-.',
                   'g':'--.', 'h':'....', 'i':'..', 'j':'.---', 'k':'-.-', 'l':'.-..', 'm':'--',
                   'n':'-.', 'o':'---', 'p':'.--.', 'q':'--.-', 'r':'.-.', 's':'...', 't':'-',
                   'u':'..-', 'v':'...-', 'w':'.--', 'x':'-..-', 'y':'-.--', 'z':'--..',
                   '0':'-----', '1':'.----', '2':'..---', '3':'...--', '4':'....-',
                   '5':'.....', '6':'-....', '7':'--...', '8':'---..', '9':'----.',
                   ' ':'/' }
        
    def encode(self, message):
        morse = []
        for letter in message:
            morse.append( self._letter_to_morse[letter.lower()] )
        return morse
In [ ]:
 

Exercise 2

Below is a copy of the School class, together with a copy of the code needed to populate an object of that class with students and exam results. Edit the School class to add in the following functions:

  • .resits() : this should return the list of exams that each student should resit if they get a "F" or "U" grade.
  • .prizeStudent() : this should return the name of the student who scored the highest average percent across all of the exams.
  • .reviseCourse(threshold) : this should return the name of the exam that gets the lowest average score across all students, if the average score is below threshold.

Use these functions to find out which students need to resit which exams, which student should be awarded the annual school prize, and which courses should be revised as the average mark is less than 50%.

In [ ]:
class School:
    def __init__(self):
        self._students = {}
        self._exams = []

    def addStudent(self, name):
        self._students[name] = Student()

    def addExam(self, exam, max_score):
        self._exams.append(exam)
        
        for key in self._students.keys():
            self._students[key].addExam(exam, Exam(max_score))
    
    def addResult(self, name, exam, score):
        self._students[name].addResult(exam, score)
        
    def grades(self):
        g = {}
        for name in self._students.keys():
            g[name] = self._students[name].grades()
        return g
In [ ]:
students = ["Charlie", "James", "Matt"]
In [ ]:
exams = { "maths" : 20, "physics" : 50, "english literature" : 30 }
In [ ]:
results = { "maths" : { "James" : 20, "Matt" : 18, "Charlie" : 4 }, 
            "physics" : { "Matt" : 48, "James" : 3 },
            "english literature" : { "Charlie" : 10, "Matt" : 25, "James" : 3 } }
In [ ]:
school = School()
In [ ]:
for student in students:
    school.addStudent(student)
In [ ]:
for exam in exams.keys():
    school.addExam(exam, exams[exam])
In [ ]:
for exam in results:
    add_results(school, exam, results[exam])
In [ ]:
school.grades()
In [ ]: