IT 117: Intermediate Scripting
Class 21
Course Work
Tips and Examples
Review
New Material
Microphone
Graded Quiz
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.
You can only take this quiz today.
There is not makeup for the weekly quiz because Gradescope does not permit it.
Solution to Homework 8
I have posted a solution to homework 8
here.
Let's take a look.
Homework 10
Homework 10 is posted
here.
It is due this coming Sunday at 11:59 PM.
Questions
Are there any questions before I begin?
Course Work
- Comments are important
- But only when they are informative
- When they tell you something that might be hard to see otherwise
- For this reason certain methods do not need to be commented
- You do not need to write comments for the following
- Constructors
- Accessors
- Mutators
- Magic Methods
- Unless they do something unusual
Tips and Examples
Don't Use "(Object)" When Defining a Class
Review
A Practical Example of A Class
- Let's create a Student class
- I need at least three values for each student
- Student ID
- Last Name
- First Name
- I will need other attributes for this class
- Preferred first name
- Phonetic spelling of the name
- Email address
- Unix username
- But they will be added later
- So the constructor sets them to the
empty string
- Here is the code for the constructor
def __init__(self, id, last_name, first_name):
self.__id = id
self.__last_name = last_name
self.__first_name = first_name
self.__preferred_name = ""
self.__phonetic_name = ""
self.__email = ""
self.__unix_username = ""
- I need
accessors
for each of these attributes
def get_id(self):
return self.__id
def get_last_name(self):
return self.__last_name
def get_first_name(self):
return self.__first_name
def get_preferred_name(self):
return self.__preferred_name
def get_phonetic_name(self):
return self.__phonetic_name
def get_email(self):
return self.__email
def get_unix_username(self):
return self.__unix_username
- The values for the last four attributes are set later ...
- after the object has been created
- So I have to create
mutators
for these attributes
def set_preferred_name(self, preferred_name):
self.__preferred_name = preferred_name
def set_phonetic_name(self, phonetic_name):
self.__phonetic_name = phonetic_name
def set_email(self, email):
self.__email = email
def set_unix_username(self, unix_username):
self.__unix_username = unix_username
Other Methods for the Student Class
- I need to create a __str__ method
for this class
def __str__(self):
return self.__first_name + " " + self.__last_name + ", " + self.__id
- Student also needs a method to return the full name
of the student
- We'll call this method get_full_name
- Normally this method will return a string consisting of the first name ...
- followed by a space ...
- followed by the last name
- But some students have a prefered first name
- In that case the prefered name will be used in place of the first name
- Here is the code
def get_full_name(self):
if self.__preferred_name:
return self.__preferred_name + " " + self.__last_name
else:
return self.__first_name + " " + self.__last_name
- I also need a method get_sort_name
def get_sort_name(self):
if self.__preferred_name:
return self.__last_name + ", " + self.__preferred_name
else:
return self.__last_name + ", " + self.__first_name
Storing Objects for Future Use
- In a previous class we wrote a script to create a dictionary of
Student objects
- But that dictionary disappeared after the script finished
- We need a way to store the students dictionary ...
- so we can use it at another time
- Most computer languages allow you to store an object on disk
- This process is called
serializing
- Python calls this process
pickling
- Every Python installation comes with a standard module named
pickle
- This module contains functions used to serialize an object
- First you must import the pickle module
import pickle
- To open a file for writing binary data we must use the
wb access mode
pickle_file = open(pickle_filename, "wb")
- To write an object to disk use the dump function
pickle.dump(students, pickle_file)
- Loading an object from a data file is a similar process
- First we open the file for binary reading
pickle_file = open(pickle_filename, "rb")
- Then we read the previously stored object using the
load function
students = pickle.load(pickle_file)
Import Statements and Global Variables
Attendance
New Material
Magic Methods
- If the truth be told, every data type in Python is actually a class
- And all variables point to instances of these classes
- When I write
x = 5
- The Python interpreter creates an integer object ...
- that holds the value 5 ...
- and the variable x that points to it
- But we can do things with numbers that do not involve using a method
- We can write expressions using arithmetic operators
x += 7
y = x + 8
x > y
- When I created the Time class
I added the difference method
- This method gives the difference between two Time
objects
- I can get the difference between two Time objects
like this
diff = t1.difference(t2)
- But wouldn't it be better to be able to write
diff = t1 - t2
- We can if we create a
magic method
for the - operator
- For every arithmetic
operator
there is an equivalent magic method
Operator | Magic Method |
+ |
__add__(self, other) |
-
| __sub__(self, other) |
* |
__mul__(self, other) |
// |
__floordiv__(self, other) |
/ |
__truediv__(self, other) |
% |
__mod__(self, other) |
** |
__pow__(self, other) |
- These magic methods allow us to use the ordinary operators of
arithmetic ...
- with objects of a class
- I can rename the difference method to
__sub__
# returns the difference in seconds between two times
def __sub__(self, other_time):
return self.__seconds - other_time.__seconds
- Now we can use the subtraction operator, - ,
with Time objects
>>> t1 = Time("10:45:10")
>>> t2 = Time("10:50:00")
>>> t2 - t1
290
- When we write
>>> t2 - t1
- The Python interpreter calls __sub__
- So we can use any arithmetic operator on class objects ...
- as long as the class contains the appropriate magic method
Magic Methods and Boolean Operators
- Boolean operators
are used to compare two things
- All these operators have corresponding magic methods
- Here they are
Operator | Magic Method |
== |
__eq__(self, other) |
!= |
__ne__(self, other) |
< |
__lt__(self, other) |
> |
__gt__(self, other) |
<= |
__le__(self, other) |
>= |
__ge__(self, other) |
- Implementing these magic methods is straightforward
def __eq__(self, other):
return self.__seconds == other.__seconds
def __ne__(self, other):
return self.__seconds != other.__seconds
def __lt__(self, other):
return self.__seconds < other.__seconds
def __gt__(self, other):
return self.__seconds > other.__seconds
def __le__(self, other):
return self.__seconds <= other.__seconds
def __ge__(self,other):
return self.__seconds >= other.__seconds
- And they work as you would expect
>>> t1 = Time("10:45:10")
>>> t2 = Time("10:50:00")
>>> t3 = Time("10:50:00")
>>> t1 == t2
False
t2 == t3
True
>>> t1 < t2
True
>>> t1 > t2
False
>>> t1 <= t2
True
>>> t2 <= t3
True
>>> t1 >= t2
False
>>> t2 >= t3
True
Type Conversion Magic Methods
- Sometimes you want to convert a value of one type into another
- This is done using one of Python's conversion functions
- All of these have corresponding magic methods
Operator |
Magic Method |
int |
__int__(self) |
float |
__float__(self) |
bool |
__bool__(self) |
str |
__str__(self) |
- We have already implemented __str__
- How we implement each of the other magic methods is up to us
- The __seconds attribute holds an integer
- So the simplest way to implement __int__ is just to
return that value
def __int__(self):
return self.__seconds
- To turn an integer into a float all we have to do is multiply by 1.0
def __float__(self):
return self.__seconds * 1.0
- But what about __bool__?
- We are under no obligation to provide this method
- But it's a good idea to do so for completeness
- In Python, any integer that is not zero is considered to be
True
>>> bool(0)
False
>>> bool(5)
True
>>> bool(-5)
True
- So we can define the __bool__ method like this
def __bool__(self):
return self.__seconds != 0
- Here is how they work
>>> t1 = Time("10:15:00")
>>> t2 = Time("00:00:00")
>>> str(t1)
'10:15:0 AM'
>>> bool(t1)
True
>>> bool(t2)
False
>>> int(t1)
36900
>>> int(t2)
0
>>> float(t1)
36900.0
- You will find the code for the latest version of the
Time class
here
More About Relational Magic Methods
- The relational magic methods for the Time class were
straightforward
- We simply applied the normal relational operators to two integer second values
def __eq__(self, other):
return self.__seconds == other.__seconds
- But what if we have two objects that are not so easily compared?
- For example two Car objects?
- Let's say the Car class has the following attributes
- __manufacturer
- __model
- __year
- __color
- What makes two Car objects equal?
- Do all of the attributes have to be equal?
- The decision is up to the programmer
- And depends on what the class will be used for
- Two cars could be equal equal if they had the same manufacturer and model
def __eq__(self, other):
return self.__manufacturer == other.get_manufacturer() and self.__model == other.get_model()
def __ne__(self, other):
return self.__manufacturer != other.get_manufacturer() or self.__model != other.get_model()
- Running the following test code
c1 = Car("Honda", "CRV", "1997", "Blue")
c2 = Car("Honda", "CRV", "2010", "Green")
c3 = Car("Honda", "Civic", "2015", "Red")
print(c1)
print(c2)
print(c3)
print("c1 == c2 :" , c1 == c2 )
print("c1 == c3 :" , c1 == c3 )
print("c1 != c3 :" , c1 != c3 )
- We get
Honda CRV 1997 Blue
Honda CRV 2010 Green
Honda Civic 2015 Red
c1 == c2 : True
c1 == c3 : False
c1 != c3 : True
Putting Test Code into Class Modules
- It is always a good idea to write test code for each class
- You could write this code as a separate script
- The script would have to import the class for testing
- But this means we need two separate files
- The class definition and the test script
- It would be simpler to put the test code in the class file
- But we have to be careful how we do this
- If we just copy the test code to the bottom of the file
- Importing the class will have side effects
- Anytime another script imports the class ...
- the test code would be run
- Here is an example
>>> from car4 import Car
Honda CRV 1997 Blue
Honda CRV 2010 Green
Honda Civic 2015 Red
c1 == c2 : True
c1 == c3 : False
c1 != c3 : True
- The test code runs as soon as we import the class
- We can prevent this from happening ...
- but we need to put this code inside a special
if
statement
if __name__ == "__main__":
c1 = Car("Honda", "CRV", "1997", "Blue")
c2 = Car("Honda", "CRV", "2010", "Green")
c3 = Car("Honda", "Civic", "2015", "Red")
print(c1)
print(c2)
print(c3)
print("c1 == c2 :" , c1 == c2 )
print("c1 == c3 :" , c1 == c3 )
print("c1 != c3 :" , c1 != c3 )
- When we run the module at the command line the test code executes
$ ./car3.py
Honda CRV 1997 Blue
Honda CRV 2010 Green
Honda Civic 2015 Red
c1 == c2 : True
c1 == c3 : False
c1 != c3 : True
- But not when we import it
>>> from car3 import Car
>>>
Do No Harm
Class Exercise
Class Quiz