IT 117: Intermediate Scripting
Class 23
Review
New Material
Graded Quiz 9
You can connect to Gradescope to take weekly graded quiz
today during the last 15 minutes of the class.
Once you start the quiz you have 15 minutes to finish it.
No Graded Quiz Next Tuesday
Since there only 1 class this week, there will be no
graded quiz next Tuesday.
There will be a graded the following Tuesday.
It will cover material in Class Notes 23, 24 and 25.
Solution to Homework 9
I have posted a solution to homework 9
here.
Let's take a look.
Homework 11
I have posted homework 11
here.
This is the last homework assignment.
It is not due this coming Sunday.
It is due the following Sunday.
Review
Inheritance
Principles of Inheritance
- For inheritance to work the two classes must have a certain relationship
- An "is a" relationship
- A truck is a vehicle
- So a Truck class can inherit from a
Vehicle class
- Truck will have all the attributes and methods of
Vehicle
- In addition to some new attributes and methods
- Whenever we use one class as the basis of another class
- We call the original class the
superclass
or the
base class
- The class which inherits is called the
subclass
or
derived class
- A subclass inherits all the attributes and methods of its superclass
An Example of Inheritance
- I have over 500 DVDs of various types
- Some are movies
- Others are television series such as Mythbusters or Nova
- I also have video lectures that I buy from The Great Courses
- I need to store different information for each type of video
- A Great Courses Video always contains 6 lessons
- Movies always have a director
- But TV series do not
The Video Class
- Let's start out by creating the superclass Video
- Here are the attributes
- Collection number
- Name
- Length
- Format
- The collection number is a unique number assigned to each video entry
- Starting with 1
- Length is the running time in minutes
- The format can either be "DVD" or "Blue Ray"
- None of these attributes will change after they have been entered
- So I need no
mutators
- I only need
accessors
- And a
constructor
The Video Constructor
- You might think the constructor would look like this
def __init__(self, collection_no, name, length, format):
self.__collection_no = collection_no
self.__name = name
self.__length = length
self.__format = format
- But I need to make sure the values of the parameters are reasonable
- So I need to create some validation methods
- Which will be called by the constuctor
Validation Methods for Video
- Although there are 4 attributes in the Video class
- The constructor will only require three values
- The value for collection_no will be calculated
- Which I will do in the next section
- I have to specify the proper values for each parameter
- Here they are
- Name - a string of at least 3 characters that are
letters or digits
- Length - an integer ranging from 15 to 1000
- Format - "DVD" or "Blue Ray"
- __check_name has to count the
characters in name that are either
letters or digits
- We do this in a
for
loop using the string method
isalnum
- If the number of characters is less than what is required
- The code will raise a ValueError exception
- The
global variable
MIN_NAME_LENGTH
will hold the minimum number of characters
- Here is the code
MIN_NAME_LENGTH = 3
class Video:
...
def __check_name(self, name):
if type(name) is not str:
raise TypeError("Video constructor expects a string argument for name")
char_count = 0
for char in name:
if char.isalnum():
char_count += 1
if char_count < MIN_NAME_LENGTH:
raise ValueError("Name must have at least " + str(MIN_NAME_LENGTH) + \
" characters that are letters or digits")
- The value for the length attribute must be an
integer
- Between 15 and 1000
- Here is the code
MIN_LENGTH = 15
MAX_LENGTH = 1000
class Video:
...
def __check_length(self, length):
if type(length) is not int:
raise TypeError("Video constructor expects a integer argument for length")
if length < MIN_NAME_LENGTH or length > MAX_LENGTH:
raise ValueError("Length must be at least " + str(MIN_LENGTH) +
" and no more than " + str(MAX_LENGTH))
- Format can be either "DVD" or "Blue Ray"
- FORMATS is a global variable holding the
legal values
- Here is the code
FORMATS = ("DVD", "Blue Ray")
class Video:
...
def __check_format(self, format):
if type(format) is not str:
raise TypeError("Video constructor expects a string argument for format")
if format not in FORMATS:
raise ValueError("Format must be one of " + str(FORMATS))
Calculating the Collection Number
- __collection_no should be calculated automatically
- I need to store the last number used
- This value will initially be set to zero
- And will be incremented for each video entry
- The value must remain even after the script ends
- So I will store it as a string in a file on disk
- The filename will be stored in the global variable
FILENAME
- __next_collection_no will
- Read the last number from the file
- Increment that number
- Save the new number to the file
- Return the new number to the calling function
- If the file does not exist, __next_collection_no
will
- Set the next number to 1
- Write the number to the file
- Return the number
- Here is the code
# reads the last collection number used from a file,
# increments that number and writes it back to the file
# and returns the incremented number
def __next_collection_no(self):
try:
file = open(FILENAME, 'r')
collection_no = int(file.readline())
collection_no += 1
file.close()
except FileNotFoundError:
collection_no = 1
finally:
file = open(FILENAME, 'w')
file.write(str(collection_no))
file.close()
return collection_no
Writing he Video Constructor
- The constructor will first call the validation methods
- Then call __next_collection_no()
- And set the attributes>
def __init__(self, name, length, format):
self.__check_name(name)
self.__check_length(length)
self.__check_format(format)
self.__collection_no = self.__next_collection_no()
self.__name = name
self.__length = length
self.__format = format
Video Accessors
The __str__ Method
New Material
The Movie Subclass
- Let's use Video to define specific subclasses
- Most of the entries in my video collection are movies
- Every movie entry will have the four attributes of the
Video class
- Collection number
- Name
- Length
- Format
- But it will also have the following additional attributes
- I must decide what attributes I want set by the constructor
- I certainly want it to set __director
and __studio
- But there will be many actors for each movie
- So I will add them later
- __actors will be a set
- Actors will be added to this set by a
mutator
Creating a Subclass
Creating a Constructor for a Subclass
- We need to give the Movie constructor 5 arguments
- name
- length
- format
- director
- studio
- But the Movie constructor will not set
the first three
- It will call the Video constructor to do that
- We need to use
dot notation
to call that constructor
- But what should we put before the dot?
- If you think about it, __init__ does not
belong to the object
- It belongs to the class itself
- So we refer to the __init__ method of the
Video class like this
Video.__init__(...)
- Then we need to set the Movie attributes
- The actors will be stored in a set
- And the names will be added after the constructor is called
- So at this point all we need to do is create an empty set
- Here is the code for the Movie constructor
def __init__(self, name, length, format, director, studio):
Video.__init__(self, name, length, format)
self.__director = director
self.__studio = studio
self.__actors = set()
- Which we test
>>> from movie import Movie
>>> m1 = Movie("Forbidden Planet", 98, "DVD", "Fred McLeod Wilcox", "MGM")
>>> str(m1)
'1: Forbidden Planet, 98 minutes, DVD'
- Notice that we did not have to create a __str__
method for Movie
- Movie is a subclass of
Video
- So it inherits the __str__ method from
Video class
- Along with the accessors
>>> m1.get_collection_no()
1
>>> m1.get_name()
'Forbidden Planet'
>>> m1.get_length()
98
>>> m1.get_format()
'DVD'
Movie Accessor Methods
- We need
accessors
for each of three new Movie attributes
def get_director(self):
return self.__director
def get_studio(self):
return self.__studio
def get_actors(self):
return self.__actors
- Which we test
>>> m1.get_director()
'Fred McLeod Wilcox'
>>> m1.get_studio()
'MGM'
>>> m1.get_actors()
[]
The add_actor Method
- The names of actors are added after the object has been created
- So we need a mutator to add the names
def add_actor(self, name):
self.__actors.add(name)
- Which we test
>>> m1.add_actor("Walter Pidgeon")
>>> m1.add_actor("Anne Francis")
>>> m1.add_actor("Leslie Nielson")
>>> m1.add_actor("Warren Stevens")
>>> m1.get_actors()
['Walter Pidgeon', 'Anne Francis', 'Leslie Nielson', 'Warren Stevens']
A __str__ Method for Movie
- Movie has more attributes than
Video
- But the __str__ it inherits from
Video will not show them
- We could try to create a new __str__ method
like this
def __str__(self):
return str(self.__collection_no) + ": " + self.__name + ", " + \
str(self.__length) + " minutes, " + self.__format + \
" Directed by " + self.__director + ", " + self.__studio
- But this won't work
>>> str(m1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/glenn/workspace-mars/it117/resources_it117/code_it117/example_code_it117/11_chapter_example_code/movie.py", line 26, in __str__
' Directed by ' + self.__director + ', ' + self.__studio
AttributeError: 'Movie' object has no attribute '_Movie__collection_no'
- The first four attributes are contained in Video
- Not in Movie
- We need a special function to access the methods in the superclass
The super
Function
- You cannot access the methods in a superclass directly
- Instead you have to use the
built-in function
super
- A call to
super
returns something like a variable
- That points to the superclass object
- You use dot notation with this variable to call the superclass's
methods
- Like this
def __str__(self):
return super().__str__() + " Directed by " + self.__director + ", " + self.__studio
- Which works when we test it
>>> str(m1)
'9: Forbidden Planet, 98 minutes, DVD Directed by Fred McLeod Wilcox, MGM'
- You will file the source code for the Movie class
here
Creating Derived Objects
- Whenever we create an object of a derived class
- We are actually creating two objects
- That behave as one
- An object of the
derived class
- And an object of the
base class
- So when we create a Movie object
- We are also creating a Video object
- The picture in memory looks like this
- The object variable m1 points to the derived object
- And
super
connects to the base class object
The Instructional Class
- Let's create another subclass of Video
- I buy videos from
The Great Courses
- The videos come in a set for a specific course
- Taught by a Professor in the field
- Each individual DVD has 6 lectures
- Of 30 minutes each
- Each course is contained on several DVDs
- And each DVD will have its own entry
- Here is the list of attributes for this class
- __course_name
- __company
- __disc_number
- __instructor
- __lectures
- The first 4 will be set by the constructor
- __lectures will be a list
- Containing the names of each of the lectures
- The entries in this list will be set with a mutator
- Why did I chose a list instead of a set?
- As I did with the Movie class
- The lectures need to be viewed in order
- The elements of a list are ordered
- But not those in a set
The Instructional Constructor
- We face the same issues in Instructional
that we faced in Movie
- But there is a new problem
- The __name field will be empty
- I'll explain why a little bit later
- The constructor for our new class must call the
Video constructor
- So we have to supply a value for name
- I will use the nonsense string "XXX" as the value
- All calls to the Video constructor
will have the same value for __name
- So I will not need a name parameter
for the Instructional constructor
- The parameters will also be in a different order
- Which makes more sense for this type of video entry
- Here is the code
def __init__(self, course_name, company, disc_number, instructor, length, format):
Video.__init__(self, "XXX", length, format)
self.__course_name = course_name
self.__company = company
self.__disc_number = disc_number
self.__instructor = instructor
self.__lectures = []
The get_name Method for Instructional
- In the Video and Movie class
the name attribute holds the name of the video
- This value is set in the Video constructor
- But I want the name for an Instructional object to be different
- I want it to have the following format
COURSE_NAME: Disc DISC_NO
- In other words, I want the name value to be calculated from other values
- Not stored in the name attribute
- This means I cannot use the version of get_name inherited from the
Video class
- This class needs its own version of get_name
def get_name(self):
return self.__course_name + ': Disc ' + str(self.__disc_number)
- Which works when I test it
>>> i1.get_name()
'Understanding the Universe: Disc 1'
The __str__ Method for Instructional
- In the Movie class __str__
called the __str__ method of Video
- And then appended the Movie attributes
- But if we call
super().__str__()
as we did in Movie
- We will get "XXX" in the output
- This is not what I want
- I want the string representation of my Instructional
videos to have a specific format
COLLECTION_NO: COURSE_NAME: Disc DISC_NO, INSTRUCTOR
- This means the only information I need from the Video superclass
- Is the collection_no
- So __str__ will call get_collection_no
from the Video class
- Along with it's own version of get_name
- As well as value of __instructor
- Here is the code
def __str__(self):
return str(super().get_collection_no()) + ': ' +self.get_name() + ', ' + self.__instructor
- Notice that I did not use all of the attributes from the superclass
- __str__ doesn't need to show all the
attributes
- It just needs to create a reasonable string representing the object
- Here is the test
>>> from instructional import Instructional
>>> i1 = Instructional("Understanding the Universe", "Great Courses", 1, "Alex Filippenko", 180, "DVD")
>>> str(i1)
'1: Understanding the Universe: Disc 1, Alex Filippenko'
Polymorphism
- The methods we declare in a superclass
- Will be available in all subclasses
- When a subclass has a method with the same name as the superclass
- The interpreter will use the subclass method
- The subclass method is said to override the superclass method
- This means that we can call any of the methods in the superclass
- On any instance of the subclass
- Even though the definition of the method can be different in each subclass
- So we can create a list of subclass objects
- Calling the same method on each one
- But getting very different results
>>> from movie import Movie
>>> m1 = Movie("Forbidden Planet", 98, "DVD", "Fred McLeod Wilcox", "MGM")
>>> from instructional import Instructional
>>> i1 = Instructional("Understanding the Universe", "Great Courses", 1, "Alex Filippenko", 180, "DVD")
>>> videos = []
>>> videos.append(m1)
>>> videos.append(i1)
>>> for video in videos:
... print(video)
... print(video.get_name())
...
2: Forbidden Planet, 98 minutes, DVD Directed by Fred McLeod Wilcox, MGM
Forbidden Planet
1: Understanding the Universe: Disc 1, Instructor Alex Filippenko
Understanding the Universe: Disc 1
- This is an example of a feature of inheritance
called polymorphism
Mutator for the Instructional Class
Accessors for the Instructional Class
- Here are the accessors for the Instructional class
def get_name(self):
return str(super().get_collection_no()) + ": " + self.__course_name + ": Disc " + \
str(self.__disc_number)
def get_course_name(self):
return self.__course_name
def get_company(self):
return self.__company
def get_disc_number(self):
return self.__disc_number
def get_instructor(self):
return self.__instructor
def get_lectures(self):
return self.__lectures
- When I test them, they work as follows
>>> i1.add_lecture("A Grand Tour of the Cosmos")
>>> i1.add_lecture("The Rainbow Connection")
>>> i1.add_lecture("Sunrise, Sunset")
>>> i1.add_lecture("Bright Objects in the Night Sky")
>>> i1.add_lecture("Fainter Phenomena in the Night Sky")
>>> i1.add_lecture("Our Sky Through Binoculars and Telescopes")
>>> i1.get_course_name()
'Understanding the Universe'
>>> i1.get_company()
'Great Courses'
>>> i1.get_disc_number()
1
>>> i1.get_instructor()
'Alex Filippenko'
>>> i1.get_lectures()
['A Grand Tour of the Cosmos', 'The Rainbow Connection', 'Sunrise, Sunset', 'Bright Objects in the Night Sky', 'Fainter Phenomena in the Night Sky', 'Our Sky Through Binoculars and Telescopes'
- You will find the source code for the Instructional
class here
The isinstance
Function
- Inheritance is a useful
- Because it lets us reuse code
- Reusing code is good practice
- The more code we write
- The greater the chance of an error
- But inheritance is not without its drawbacks
- What if you have an object that you know is a subclass of
Video
- But you don't know which subclass?
- Each subclass has its own unique methods
- If you call a method on an object that does not implement the method
- The interpreter will raise an exception
- What can you do?
- You can use the
isinstance
built-in function
- The general format for using this function is
isinstance(OBJECT_VARIABLE, CLASS_NAME)
isinstance
is a boolean function that returns True or False
>>> isinstance(i1, Movie)
False
>>> isinstance(i1, Instructional)
True
isinstance
will also return True
- When you use the class name of the superclass
>>> isinstance(i1, Video)
True
Fairness
Attendance
Graded Quiz
Class Exercise
Class Quiz