Example - 1

In [8]:
import datetime

class Person(object):
    
    def __init__(self, name):
        """create a person called name"""
        self.name= name
        self.birthday= None
        self.lastName= name.split(' ')[-1]
        
    def getLastName(self):
        """return self's last name"""
        return self.lastName
    
    def __str__(self):
        """return self's name"""
        return self.name
    
    def setBirthday(self,month,day,year):
        """sets self's birthday to birthDate"""
        self.birthday= datetime.date(year,month,day)
        
    def getAge(self):
        """returns self's current age in days"""
        if self.birthday == None:
            raise ValueError
        return (datetime.date.today() -self.birthday).days
    
    def __lt__(self, other):
        """return True if self's name is lexicographically
         less than other's name, and False otherwise"""
        if self.lastName == other.lastName:
            return str(self.name) < str(other.name)
        return str(self.lastName) < str(other.lastName)
In [9]:
p1 = Person('Mark Zuckerberg')
p1.setBirthday(5,14,84)

p2 = Person('Drew Houston')
p2.setBirthday(3,4,83)

p3 = Person('Bill Gates')
p3.setBirthday(10,28,55)

p4 = Person('Andrew Gates')

p5 = Person('Steve Wozniak')

personList= [p1, p2, p3, p4, p5]
In [10]:
for e in personList:
    print(e)
Mark Zuckerberg
Drew Houston
Bill Gates
Andrew Gates
Steve Wozniak
In [11]:
personList.sort()

for e in personList:
    print(e)
Andrew Gates
Bill Gates
Drew Houston
Steve Wozniak
Mark Zuckerberg
In [12]:
class MITPerson(Person): #sub class of person 
    nextIdNum= 0 # next ID number to assign
    def __init__(self, name):
        Person.__init__(self, name) # initialize Person attributes
        self.idNum= MITPerson.nextIdNum# MITPersonattribute: unique ID
        MITPerson.nextIdNum+= 1
    def getIdNum(self):
        return self.idNum
        # sorting MIT people uses their ID number, not name!
    def __lt__(self, other):
        return self.idNum< other.idNum
    def speak(self, utterance):
        return(self.getLastName() + " says: " + utterance)
In [14]:
m3 = MITPerson('Mark Zuckerberg')
Person.setBirthday(m3,5,14,84)

m2= MITPerson('Drew Houston')
Person.setBirthday(m2,3,4,83)

m1 = MITPerson('Bill Gates')
Person.setBirthday(m1,10,28,55)

MITPersonList= [m1, m2, m3]
In [17]:
for e in MITPersonList:
    print(e) # prints in the order in the list 
Bill Gates
Drew Houston
Mark Zuckerberg
In [19]:
MITPersonList.sort()
for e in MITPersonList:
    print(e) #prints in the order is id as designed in __it__
Mark Zuckerberg
Drew Houston
Bill Gates
In [21]:
p1 = MITPerson('Eric')
p2 = MITPerson('John')
p3 = MITPerson('John')
p4 = Person('John')
In [23]:
p1 < p2 #compares id
Out[23]:
True
In [24]:
p1 < p4 # p1 belongs to MITPerson but P4 belongs to Person only. since p1 is used first , tries to compare on ids
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-24-20a2383c81c5> in <module>()
----> 1 p1 < p4

<ipython-input-12-6a5e51a6fdef> in __lt__(self, other)
      9         # sorting MIT people uses their ID number, not name!
     10     def __lt__(self, other):
---> 11         return self.idNum< other.idNum
     12     def speak(self, utterance):
     13         return(self.getLastName() + " says: " + utterance)

AttributeError: 'Person' object has no attribute 'idNum'
In [25]:
p4 < p1 # since p4 belongs Person class both are compared based on Person class comparison 
Out[25]:
False

WHO INHERITS?

Why does p4 < p1 work, but p1 < p4 doesn’t?

  • p4 < p1 is equivalent to p4.__lt__(p1), which means we use the __lt__ method associated with the type of p4, namely a Person (the one that compares based on name)
  • p1 < p4 is equivalent to p1.__lt__(p4), which means we use the __lt__ method associated with the type of p1, namely an MITPerson(the one that compares based on IDNum) and since p4 is a Person, it does not have an IDNum
In [27]:
class Student(MITPerson):
    pass
class UG(Student):
    def __init__(self, name, classYear):
        MITPerson.__init__(self, name)
        self.year= classYear
    def getClass(self):
        return self.year
    def speak(self, utterance):
        return MITPerson.speak(self, " Dude, " + utterance)
class Grad(Student):
    pass
class TransferStudent(Student):
    pass
def isStudent(obj):
    return isinstance(obj,Student)
In [31]:
s1 = UG('Matt Damon', 2017)
s2 = UG('Ben Affleck', 2017)
s3 = UG('Lin Manuel Miranda', 2018)
s4 = Grad('Leonardo di Caprio')
S5 = TransferStudent('Robert deNiro')
print(s1)
print(s1.getClass())
print(s1.speak('where is the quiz?'))
print(s2.speak('I have no clue!'))
Matt Damon
2017
Damon says:  Dude, where is the quiz?
Affleck says:  Dude, I have no clue!

HIERARCHY

      Person (Superclass)

          ↑

     MITPerson

          ↑

     Student

    ↑    ↑    ↑

UG Grad transfer

In [33]:
class Professor(MITPerson):
    def __init__(self, name, department):
        MITPerson.__init__(self, name)
        self.department= department
    def speak(self, utterance):
        new = 'In course ' + self.department+ ' we say '
        return MITPerson.speak(self, new + utterance)
    def lecture(self, topic):
        return self.speak('it is obvious that ' + topic)
In [35]:
faculty = Professor('Doctor Arrogent','six')
In [37]:
print(m1.speak('hi there')) # Person instance 
print(s1.speak('hi there')) # student instance
print(faculty.speak('hi there')) # Professor instance 
print(faculty.lecture('hi there')) # Professor instance 
Gates says: hi there
Damon says:  Dude, hi there
Arrogent says: In course six we say hi there
Arrogent says: In course six we say it is obvious that hi there

HIERARCHY

      Person (Superclass)

          ↑

     MITPerson   ←  Professor

          ↑

     Student

    ↑    ↑    ↑

UG Grad transfer

Example - 2

In [92]:
class Grades(object):
    """A mapping from students to a list of grades"""
   
    def __init__(self):
        """Create empty grade book"""
        self.students= [] # list of Student objects
        self.grades= {} # maps idNum-> list of grades
        self.isSorted= True# true if self.studentsis sorted
        
    def addStudent(self, student):
        """Assumes: student is of type Student
        Add student to the grade book"""
        if student in self.students:
            raise ValueError('Duplicate student')
        self.students.append(student)
        self.grades[student.getIdNum()] = []
        self.isSorted= False
        
    def addGrade(self, student, grade):
        """Assumes: grade is a float
        Add grade to the list of grades for student"""
        try:
            self.grades[student.getIdNum()].append(grade)
        except KeyError:
            raise ValueError('Student not in grade book')
            
    def getGrades(self, student):
        """Return a list of grades for student"""
        try: # return copy of student's grades
            return self.grades[student.getIdNum()][:]
        except KeyError:
            raise ValueError('Student not in grade book')
    
    def allStudents(self):
        """Return a list of the students in the grade book"""
        if not self.isSorted:
            self.students.sort()
            self.isSorted= True
        return self.students[:]
    
    def gradeReport(course):
        """Assumes: course is of type grades"""
        report = []
        for s in course.allStudents():
            tot = 0.0
            numGrades= 0
            for g in course.getGrades(s):
                tot += g
                numGrades+= 1
            try:
                average = tot/numGrades
                report.append(str(s) + '\'s mean grade is '+ str(average))
            except ZeroDivisionError:
                report.append(str(s) + ' has no grades')
        return '\n'.join(report)
    
In [93]:
#creating students 

ug1 = UG('Matt Damon', 2018)
ug2 = UG('Ben Affleck', 2019)
ug3 = UG('Drew Houston', 2017)
ug4 = UG('Mark Zuckerberg', 2017)
g1 = Grad('Bill Gates')
g2 = Grad('Steve Wozniak')
In [94]:
# adding students to course six00
six00 = Grades()
six00.addStudent(g1)
six00.addStudent(ug2)
six00.addStudent(ug1)
six00.addStudent(g2)
six00.addStudent(ug4)
six00.addStudent(ug3)
In [95]:
# adding grades to students 
six00.addGrade(g1, 100)
six00.addGrade(g2, 25)
six00.addGrade(ug1, 95)
six00.addGrade(ug2, 85)
six00.addGrade(ug3, 75)
In [96]:
print(Grades.gradeReport(six00))
Matt Damon's mean grade is 95.0
Ben Affleck's mean grade is 85.0
Drew Houston's mean grade is 75.0
Mark Zuckerberg has no grades
Bill Gates's mean grade is 100.0
Steve Wozniak's mean grade is 25.0
In [97]:
#update the grades
six00.addGrade(g1, 90)
six00.addGrade(g2, 45)
six00.addGrade(ug1, 80)
six00.addGrade(ug2, 75)
In [99]:
print(Grades.gradeReport(six00)) # Avg if Bill has come down , while Wozniak's has gone up.
Matt Damon's mean grade is 87.5
Ben Affleck's mean grade is 80.0
Drew Houston's mean grade is 75.0
Mark Zuckerberg has no grades
Bill Gates's mean grade is 95.0
Steve Wozniak's mean grade is 35.0

USING EXAMPLE

Could list all students using

for s in six00.allStudents():

  • print(s)

Prints out the list of student names sorted by idNum

Why not just do

for s in six00.students:

  • print(s)

Violates the data hiding aspect of an object, and exposes internal representation

  • If I were to change how I want to represent a grade book, I should only need to change the methods within that object, not external procedures that use it

COMMENTS ON EXAMPLE

  • nicely separates collection of data from use of data
  • access is through methods associated with the gradebookobject
  • but current version is inefficient –to get a list of all students, I create a copy of the internal list

GENERATORS

In [100]:
def genTest():
    yield 1
    yield 2

Generators have a next() method which starts/resumes execution of the procedure. Inside of generator:

  • yield suspends execution and returns a value
  • returning from a generator raises a StopIteration exception

The difference is that, while a return statement terminates a function entirely, yield statement pauses the function saving all its states and later continues from there on successive calls.

In [101]:
foo = genTest()
foo.__next__()
Out[101]:
1
In [102]:
foo.__next__()
Out[102]:
2
In [104]:
foo.__next__() # throws error after 2 iteration
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-104-aab3b25e378d> in <module>()
----> 1 foo.__next__() # throws error after 2 iteration

StopIteration: 
In [105]:
for n in genTest():
    print(n)
1
2
In [106]:
def genFib():
    fibn_1 = 1 #fib(n-1)
    fibn_2 = 0 #fib(n-2)
    while True:
    # fib(n) = fib(n-1) + fib(n-2)
        next = fibn_1 + fibn_2
        yield next
        fibn_2 = fibn_1
        fibn_1 = next
In [107]:
fib = genFib()
fib.__next__()
Out[107]:
1
In [108]:
fib.__next__() # everytime gives next fib number 
Out[108]:
2
In [109]:
fib.__next__()
Out[109]:
3
In [110]:
fib.__next__()
Out[110]:
5
In [111]:
fib.__next__()
Out[111]:
8

WHY GENERATORS?

  • generator separates the concept of computing a very long sequence of objects, from the actual process of computing them explicitly
  • allows one to generate each new objects as needed as part of another computation (rather than computing a very long sequence, only to throw most of it away while you do something on an element, then repeating the process)
  • have already seen this idea in range

FIX TO GRADES CLASS

In [113]:
#BEFORE
def allStudents(self):
    if not self.isSorted:
        self.students.sort()
        self.isSorted= True
    return self.students[:]
#return copy of list of students

#AFTER
def allStudents(self):
    if not self.isSorted:
        self.students.sort()
        self.isSorted= True
    for s in self.students:
        yield s

Reference

  • edX course offered by MIT
  • 6.00.1x Introduction to Computer Science and Programming Using Python