Lecture 10: Object-Oriented Programming
In this lecture, we will cover the last bit of Well, object-oriented programming gives us both. We will be able to define some new custom types that (1) contain some data and (2) have functions defined for our custom types. |
Everything in Python is an object.This means the types we have seen so far (integers, floats, strings, lists, dictionaries, tuples) are all objects. |
a first attempt: writing our own Snapchat
Suppose we want to write our own Snapchat application: let's call it
|
Of course, there are many more features we may like our MiddSnap
application to have, but let's just stick to the above list for now.
I intentially made some words in bold-faced blue and some words in bold-faced red.
Do you see the difference?
Actually, the bold-faced blue words are functions we would like our application to achieve, whereas the bold-faced red words are data we would like to store in our application. Of course, the functions operate on some of these pieces of data, so the two are related. Let's focus on the data for now. You might notice the word "contact" appears several times, for example, we store a "list of contacts", or we send/receive something to/from a contact. How should we represent a contact? Specifically, what data do you need for a contact? How about:
- first name
- last name
- username
- phone number
- avatar
- number of points
Okay, well maybe we can keep a list of strings for the first name, a list of strings for the last name, a list of strings for the username, a list of integers for the phone number, a list of MiddImage
's for the avatar, and a list of integers for the score.
Yikes - that's a lot of lists.
Maybe we can use some dictionaries, but that doesn't get us much further.
Wouldn't it be nice if we just had some kind of a data structure that represents a contact with all the aforementioned information?
First, let's make things a bit worse.
What if we want to store an actual Snap (maybe in our list of memories)?
The most obvious thing we should store for a Snap is an image.
So maybe we can just store a list of images as our memories.
But wait - to reconstruct the memory, some snaps have text, some don't, and some might have some drawings.
So we should probably store an extra list of booleans like has_text
or has_drawing
to fully reconstruct the Snap whenever we want to see our memory again.
And then we can store the actual text as a list of strings and the drawing as another list of images.
Since some snaps may or may not have some text or drawing, this adds a bit of complication to the data we need to keep track of.
Again, it would be really nice if we could just store some all-encompassing piece of data for a snap that contains the image, text, drawing, etc.
Luckily, we can address all of the concerns we had when developing Next we would define some functions that are designed specifically for each object. For example, we would define a draw function for a snap. This function would be specific to a "snap" object - it doesn't make sense to call some "draw" function for a "contact."
In |
A class definition is like the blueprint for a house. |
Any house is a constructed instance of the blueprint, maybe with a different wall color or door color.
attributes & methods
Before we jump into some defining a class
To define a class, we need to tell
Just like with functions, you should place a docstring immediately after your class declaration (the next line after the semicolon).
Thus, typing
Okay, now we're ready to define some methods.
The first method you will always (always, always, always) define is the
But where am I saving these attributes you ask???
Ah, the |
creating an object
Remember when you type x = 2
, this really means "create an integer and store it in a variable called x
".
Well, the truth is that x
is an object of class int
(recall the output of type(x)
).
This means that somewhere deep down in Python
there is a class definition for an integer.
In fact, all the types we have seen so far are objects: floats, strings, lists, dictionaries, etc.
Everything in Python
is an object!
Recall the methods we used to create an empty list (L = list()
) or an empty dictionary (D = dict()
).
In fact, we are creating objects that are instances of a list
or dict
class.
The same is true for our own custom classes - we create objects by calling obj = ClassName()
.
When you do so, you are actually calling the __init__
function defined for that class.
You can pass additional arguments into your call to obj = ClassName()
, depending on whether your constructor requires input arguments.
Note that the order these come in is the same order as they are listed after the self
.
accessing attributes and calling methods using an OBJECT
Now, suppose that you have created an instance of your class ClassName
, using obj = ClassName()
.
In order to access the attributes of the class (those defined in the __init__
method), we will use dot notation.
For the example above, we could access obj.attribute1
and obj.attribute2
.
In order to call a method of a class (those defined within the block of code in the class definition), we also use dot notation.
In the same example, we would call obj.method1()
or obj.method2()
(remember to use parentheses since these are functions).
When Python
sees this, it implicitly passes in obj
as the self
variable as the first argument in the function definition.
I know - this is pretty confusing, and will hopefully make more sense once we practice with these concepts.
accessing attributes and calling methods from inside a CLASS definition
What if we want to access attribute1
from inside of method1
?
Well, since we passed in a reference to the object (the first argument, i.e. self
, to method1
), then we can access attribute1
of self
directly!
Remember, self
is an object (an instance of the class) too, so we can access attributes and methods in the same way as the previous section.
Thus, calling method1
from method2()
would look like self.method1()
.
And calling method2
from method1()
would look like self.method2()
.
everything in Python
is an object
It's time for me to come clean again!
You have been using objects all along!
Remember when we typed type(2)
or type(1.5)
and saw <class 'int'>
and <class 'float'>
in return?
This is because numbers, lists, dictionaries, strings, etc. are all objects.
You can use the dir
function to list out all the methods for one of these built-in types.
Remember how I kept using the word "object" when talking about MiddImage
?
It's because they're objects too!
If you open up middimage.py
you'll see the class definition for MiddImage
.
A MiddImage
object has attributes for the width
, height
, channels
and the pixels (stored in an attribute called _data
, but don't worry about this part).
Another time we saw objects was actually with the turtle
module!
In fact, we were using an instance of the Turtle
class.
This is useful if we want to have multiple turtles - we could create several instances of the Turtle
class.
Another useful class included in the turtle
module is the Screen
class.
For more information, type help(turtle)
after importing the module.
Before building more advanced applications with object-oriented programming, let's study some smaller applications.
In this example, we will write a class to represent a point in $2d$.
We will break up this example into a few parts.
In this first part, we want to write a Point
class that simply represents a point in the plane with $x$ and $y$ coordinates.
In part II (the next section), we will add some attributes and methods to visualize these points as dots.
When defining a class, you should ask yourself a few questions:
- What data (attributes) would I like to store in an instance of my class?
- What functions (methods) would I like to use for instances of my class?
In this first part, we'll keep things simple. We will just store the $x$ and $y$ coordinates of the point. We will also provide methods to calculate (1) the distance between the point and the origin, and (2) the distance from one point to another. We will also provide a method to print out the coordinates of the point in a nice way.
Note that we used the __repr__
function to print some information about a Point
object.
This is the function that is called when you type print(pt)
, where pt
is an instance of the Point
class.
In fact, it's equivalent to calling print( pt.__repr__() )
.
Functions with two underscores (__
) on either side of the function name are special functions that Python
looks for when you try to (1) use other built-in functions on your custom objects (such as print
, str
or len
) or (2) perform operations on your custom objects.
Yes, this means you can define +
for your custom objects!
You can get a sense of what kinds of special functions you can write for your custom types by typing dir(2)
or dir([])
.
You can also overload the __str__
function so that Python
will print a string representation of your object when you pass your object to the print
function.
In the last example, we used the special __repr__
function to print out some information about Point
objects.
Let's now add a method to draw a point using the turtle
module.
We will draw a particular Point
object as a dot (using turtle.dot
), and keep track of two additional attributes in the Point
class: the size
of the dot, and the color
of the dot.
We will also modify the to_string
method since we don't want to print the 'Hi I'm a Point with coordinates ...
part.
We will use this to_string
method to optionally write the coordinates at the location of the dot - this is useful when debugging graphics programs!
Let's do another geometric application (I like geometric applications because they really help you visualize the concepts).
This time, we will write a Triangle
class which keeps track of the bottom left corner of the triangle $(x,y)$ and the side length $L$.
We will also store a "tilt angle" which is the angle the bottom side of the triangle makes with the x-axis.
A color can also be provided (defaults to 'blue'
if no color provided) that will be used when drawing a triangle object with the turtle
.
Thus we will write a draw
method for the Triangle
class.
It may also be useful to write area
and perimeter
methods, but we'll skip this for now.
What other methods do you think would be useful to define for the Triangle
class?
© Philip Claude Caplan, 2021