Lecture
13
Class and Objects
1. Introduction
Having used some
of Python’s built-in types, we are ready to create a userdefined type:
the Point.
An alternative
is to define a
new user-defined compound type, also called a class. This approach
involves a bit more effort, but it has advantages that will be apparent
soon. A class definition looks like this:
Class
definitions can appear anywhere in a program, but they are usually near
the beginning (after the import statements). The syntax rules for a
class definition are the same as for other compound statements
This definition
creates a new class
called Point. The pass statement has no effect; it is only necessary
because a compound statement must have something in its body. By
creating the Point class, we created a new type, also called Point. The
members
of this type are called instances of the type or objects.
Creating
a new instance is called instantiation. To instantiate a Point
object, we call a function named (you guessed it) Point
The
variable blank is assigned a reference to a new Point object. A
function like Point that creates new objects is called a constructor.
Here a pretty
good chart to show you the relationshipo between an object and the
class:
2.
Attributes
We can add new
data to an instance using dot notation:
This
syntax is similar to the syntax for selecting a variable from a module,
such as math.pi or string.uppercase. In this case, though, we are
selecting a data item from an instance. These named items are called
attributes.
The following
state diagram shows the result of these assignments:
The variable
blank refers to a Point object, which contains two attributes. Each
attribute refers to a floating-point number.
The
expression blank.x means, "Go to the object blank refers to and get the
value of x." In this case, we assign that value to a variable named x.
There
is
no conflict between the variable x and the attribute x. The purpose of
dot notation is to identify which variable you are referring to
unambiguously.
3.
Sameness
The
meaning of the word \same" seems perfectly clear until you give it some
thought, and then you realize there is more to it than you expected.
For example, if you say, Chris and I have the same car," you mean that
his car and yours are the same make and model, but that they are two
different cars.
If
you say, Chris and I have the same mother," you mean that his mother
and yours are the same person. So the idea of "sameness" is different
depending on the context. When you talk about objects, there is a
similar ambiguity. For example, if two Points are the same, does that
mean they contain the same data (coordinates) or that they are actually
the same object? To find out if two references refer to the same
object, use the == operator. For example:
Even
though p1 and p2 contain the same coordinates, they are not the same
object. If we assign p1 to p2, then the two variables are aliases of
the same object:
However, if you
can make a function to compare the contents of two objects.
4.
Rectangles
For example, a
new 'Rectangle' class can be created.
To instantiate
the object by 'box = Rectangle()', 'box.corner = Point()'.
This new object
is instatiated by the class.
This
code creates a new Rectangle object with two floating-point attributes.
To specify the upper-left corner, we can embed an object within an
object
The dot operator
composes.
The expression box.corner.x means, Go to the object box refers to and
select the attribute named corner; then go to that object and select
the attribute named x."
The
figure shows the state of this object:
5.
Objects are
mutable
We
can change the state of an object by making an assignment to one of its
attributes. For example, to change the size of a rectangle without
changing its position, we could modify the values of width and height:
We
could encapsulate this code in a method and generalize it to grow the
rectangle by any amount
6.
Copying
Assignment
statements in Python do not copy objects, they create bindings between
a target and an object. For collections that are mutable or contain
mutable items, a copy is sometimes needed so one can change one copy
without changing the other. This module provides generic shallow and
deep copy operations (explained below).
Interface
summary:
copy.copy(x)
Return a shallow copy of x.
copy.deepcopy(x[,
memo]) Return a deep copy of x.
A shallow copy
constructs a
new compound object and then inserts
references into it to the objects found in the original. A deep copy
constructs a new compound object and then, recursively, inserts copies
into it of the objects found in the original. Once
we import the copy module, we can use the copy method to make a new
Point. p1 and p2 are not the same point, but they contain the same data.
A shallow copy
means constructing a new collection object and then populating it with
references to the child objects found in the original. The copying
process does not recurse and therefore won’t create copies of the child
objects themselves. In case of shallow copy, a reference of object is
copied in other object. It means that any changes made to a copy of
object do reflect in the original object. In python, this is
implemented using “copy()” function.
Deep copy
is a process in which the copying process occurs recursively. It means
first constructing a new collection object and then recursively
populating it with copies of the child objects found in the original.
In case of deep copy, a copy of object is copied in other object. It
means that any changes made to a copy of object do not reflect in the
original object. In python, this is implemented using “deepcopy()”
function.
Important
Points: The difference between shallow and deep copying is only
relevant for compound objects (objects that contain other objects, like
lists or class instances):
*
A shallow copy constructs a new compound object and then (to the extent
possible) inserts references into it to the objects found in the
original.
*
A deep copy constructs a new compound object and then, recursively,
inserts copies into it of the objects found in the original.
For
short, a 'shallow copy' will generate a new variable (memory space) and
share the same data (the original data). However, a 'deep copy' will
duplicate the entire variable and data into a new one.
Two charts to help you understand the differences:
An example about deep copy:
An example about shallow copy:
To
copy a simple object like a Point, which doesn’t contain any embedded
objects, copy is sufficient.
This is called shallow copying. For something like a Rectangle, which
contains a reference to a Point, copy doesn’t do quite the right thing.
It copies the reference to the Point object, so both the old Rectangle
and the new one refer to a single Point. If we create a box, b1, in the
usual way and then make a copy, b2, using copy, the resulting state
diagram looks like this:
This is almost
certainly not
what we want. In this case, invoking growRect on one of the Rectangles
would not affect the other, but invoking moveRect on either would
affect both! This behavior is confusing and error-prone. Fortunately,
the copy module contains a method named deepcopy that copies not only
the object but also any embedded objects. You will not be surprised to
learn that this operation is called a deep copy.
7. Classes and Functions
There
is a
very simple function to calculate the current time. The original time
was provided and the duration of the event is t1 hours and t2 minutes.
The following code will calculate the current time and convert it into
a certain format.
The pass
statement is a null operation; nothing happens when it executes.
Now
let's move the function into the class to make the function a 'method'
for the class 'Time'. This is like equip the class 'Time' with a skill
of this function.
Yes, we got the same result and this works.
The
question is why we use 'Time.addTime(0,2)' to call this method but not
using 'time.addTime(0,2)' to call this method? Will 'time.addTime(0,2)
work?
The answer is no:
Why
is that? When you use 'Time.addTime()', the class 'Time' has this
method as defined and will not carry any other arguments in the
function of 'addTime(t1,t2)'.
However,
'time.addTime(t1,t2)' looks like only has two arguments but it actually
has THREE! The third one is the object 'time' itself. All the
artributes in a package with object 'time' will be treated as one
single argument for the function 'addTime(t1,t2)'.
So
now, 'time.addTime(0,2)' has three arguments, '0', '2', and 'time'
itself. However in the function definition, 'def addTime(t1,t2)' only
has two arguments. This is why I got the errors.
Below
shows how I fixed it. The details about how to use 'self' will be
introduced later. The 'self' argument will represent the package of the
object 'time' itself.
The
only change here is to add an additional argument 'self' inside the
function. This will bring the attributes of 'time' into this function.
To make the function more general, just change all the 'time' word inside the function into 'self':
For the following examples, let's don't use too much of 'self' in there. Let's make it easier:
Now you know the
concept of adding more time to the starting time.
Given
the starting time of a movie, and the duration of a movie, the
following function will figure out when the movie will be done:
The '%.2d' keeps the 2 digits of accuracy.
The
result, 10:80:00 is not be what you were hoping for. The problem is
that this function does not deal with cases where the number of seconds
or minutes adds up to more than sixty. When that happens, we have to
“carry” the extra seconds into the minute column or the extra minutes
into the hour column.
The parentheses in 'class Time():' is redundant. You can delete it if you like.
However, what if
the movie is pretty long and there are carries in 'hour'.
The same
concept, just make the following modification:
8.
An Alternative Method to Convert the Time
An
alternative is designed development, in which high-level insight into
the problem can make the programming much easier. In this case, the
insight is that a Time object is really a three-digit number in base 60
(see http://en.wikipedia.org/wiki/Sexagesimal.)! The second attribute
is the “ones column”, the minute attribute is the “sixties column”, and
the hour attribute is the “thirty-six hundreds column”.
We can convert
Time objects to integers and take advantage of the fact that the
computer knows how to do integer arithmetic.
(Again, the
parentheses in 'class Time():' is redundant)
Tasks:
1. Design a method/methods in
the Class 'Time' that takes the input from the user (use the input()
function) in the format of 'XX:XX:XX' as the start time of the exam and return the start time to a variable.
You must use a data format checking method to verify if the input from the user is valid.
There are something you may need to use:
-------------------------------------------
a. The 'ord()' function will convert your data into its UTF-8 code.
b. UTF-8 encoding table and Unicode characters:
https://www.utf8-chartable.de/unicode-utf8-table.pl?utf8=dec
c. Split a long line into multiple lines:
https://stackoverflow.com/questions/14417571/breaking-a-line-of-python-to-multiple-lines
-------------------------------------------
2.
Based on the solution you have in Problem 1, add more methods to Class
'Time' to calculate the end time of the exam based on the start time
and the duration. Print out the end time.