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.