Lecture 8: Object Oriented Programming

Flash and JavaScript are required for this feature.

Download the video from iTunes U or the Internet Archive.

Description: In this lecture, Dr. Bell introduces Object Oriented Programming and discusses its representation in Python.

Instructor: Dr. Ana Bell

The following content is provided under a Creative Commons license. Your support will help MIT OpenCourseWare continue to offer high quality educational resources for free. To make a donation or view additional materials from hundreds of MIT courses, visit MIT OpenCourseWare at ocw.mit.edu.

PROFESSOR: All right everyone. Let's get started. So today's lecture and Wednesday's lecture, we're going to talk about this thing called object oriented programming. And if you haven't programmed before, I think this is a fairly tough concept to grasp. But hopefully with many, many examples and just by looking at the code available from lectures, you'll hopefully get the hang of it quickly. So let's talk a little bit about objects. And we've seen objects in Python so far. Objects are basically data in Python. So every object that we've seen has a certain type. OK, that we know.

Behind the scenes, though, every object has these two additional things. One is some data representation. So how Python represents the object just behind the scenes and what are different ways that you can interact with the object. So for example, every one of these is a different object. For example, this is the number 1,234. It's a specific object that is of type integer. The number 5 is a different object that's of type integer and so on.

We've seen floats. We've seen strings. We've seen lists. Lists and dictionaries are more complicated objects. Object types. Sorry. But every object has a type, some sort of way that it's represented in Python and some ways that we can interact with them.

OK. So the idea behind object oriented programming is, first of all, everything in Python is an object. We've said that before and in this lecture I think we'll really get at what that means. So we've seen strings, integers, dictionaries, lists. Those are all objects. When we did functions, we saw that we could pass as a parameter another function. So functions were also objects in Python. So literally everything in Python is an object.

So what are the kinds of things we can do with objects? Well, once you have a type, you can create a new object that is of some type. And you can create as many objects as you'd like of that particular type, right? An integer 5 and integer 7. Those all work in a program. Once you've created these new objects, you can manipulate them. So for a list, for example, you can append an item to the end of the list, you can delete an item, remove it, concatenate two lists together. So that's ways that you can interact with objects.

And the last thing you can do is you can destroy them. So and with lists, we saw explicitly that you can delete elements from a list, or you can just forget about them by reassigning a variable to another value, and then at some point, Python will collect all of these dead objects and reclaim the memory.

So let's continue exploring what objects are. So let's say I have these two separate objects. One is a blue car. One is a pink car. So objects are really data abstractions. So these two cars can be created by the same blueprint. OK? This is a blueprint for a car and if an object is a data abstraction, there's two things that this abstraction is going to capture. The first is some sort of representation. What is going to represent the car, what data represents a car object? And the second is what are ways that we can interact with the object?

So if we think about a car blueprint, some general representation for a car could be the number of wheels it has, the number of doors it has, maybe its length, maybe its height, so this is all part of what data represents the car. OK? The interface for the car is what are ways that you can interact with it. So for example, you could paint a car, right? So you could change its color. You could have the car make a noise and different cars might make different noises. Or you can drive the car, right? So these are all ways that you can interact with the car.

Whereas the representation are what makes up the car. What data abstractions make up the car. Let's bring it a little closer to home by looking at a list. So we have this data type of list, right? We've worked with lists before. The list with elements 1, 2, 3, and 4 is a very specific object that is of type list. Again, we think about it in terms of two things. One is what is the data representation of the list? So behind the scenes how does Python see lists?

And the second is, how do you interact with lists? So what are ways that you can manipulate a list object once it's created? So behind the scenes you have a list, L, which is going to be made up of essentially two things. One is going to be the value at specific index. OK? So at index 0, it has the value 1, right, because it's the first element in the list. And the second thing that represents a list is going to be this second part, which is a pointer. And internally this pointer is going to tell Python where is the memory location in the computer where you can access the element index 1.

So it's just essentially going to be a chain, going from one index to the other. And at the next memory location you have the value at index 1, and then you have another pointer that takes you to the location in memory where the index 2 is located. And in index 2 you have the value and then the next pointer, and so on and so on. So this is how Python internally represents a list. OK?

How you manipulate lists, we've done this a lot, right? You can index into a list, you can add two lists together, you can get the length, you can append to the end of a list, you can sort a list, reverse a list, and so many other things, right? So these are all ways that you can interact with the list object as soon as you've created it. So notice both of these, the internal representation and how you manipulate lists, you don't actually know internally how these are represented, right? How did whoever wrote the list class decide to implement a sort. We don't know.

You also weren't aware of how these lists were represented internally. And you didn't need to know that. That's the beauty of object oriented programming and having these data abstractions. The representations are private of these objects and they are only known by what you can find out how it's done, but they only should be known by whoever implemented them. You, as someone who uses this class, doesn't really need to know how a list is represented internally in order to be able to use it and to write cool programs with them. OK?

So just find a motivation here before we start writing our own types of objects is the advantages of object oriented programming is really that you're able to bundle this data, bundle some internal representation, and some ways to interact with a program into these packages. And with these packages, you can create objects and all of these objects are going to behave the exact same way. They're going to have the same internal representation and the same way that you can interact with them. And ultimately, this is going to contribute to the decomposition and abstraction ideas that we talked about when we talked about functions. And that means that you're going to be able to write code that's a lot more reusable and a lot easier to read in the future. OK.

So just like when we talked about functions, we're going to sort of separate the code that we talk about today into code where you implement a data type and code where you use an object that you create. OK? So remember when we talked about functions, you were thinking about it in terms of writing a function, so you had to worry about the details of how you implement a function. And then you had to worry about just how to use a function, right? So it's sort of the same idea today. So when you're thinking about implementing your own data type, you do that with this thing called a class. And when you create a class, you're basically going to figure out what name you want to give your class and you're going to find some attributes. And attributes are going to be the data representation and ways that you can interact with your object.

So you, as the programmer of this class, are going to decide how you want people to interact with the object and what data this object is going to have. So for example, someone wrote code that implements a list class, right, and we don't actually know how that was done. But we can find out. So creating the class is implementing the class and figuring out data representation and ways to interact with the class. Once that's done, you can then use your class. And you use the class by creating new instances of the class.

So when you create a new instance, you essentially create a new object that has the type, the name of your class. And you can create as many objects as you'd like. You can do all the operations that you've defined on the class. So for example, someone wrote the code to implement list class and then you can just use the list class like this. You can create a new list, you can get the length pf the list, you can append to the end of the list, and so on and so on.

So let's start defining our own types, OK? So now you're going to define classes, you're going to write classes which are going to define your own types of objects. So for today's lecture we're going to look at code that's going to be in the context of a coordinate object. And a coordinate object is essentially going to be an object that's going to define a point in an xy plane. So x, y is going to be a coordinate in a 2D plane. So we're going to write code that's going to allow us to define that kind of object.

So the way we do that is we have to define a class. So we have to tell Python, hey, I'm defining my own object type. So you do that with this class key word. So you say class, then you say the name of your type. In this case, we're creating a type called coordinate. Just like we had type list, type string, and so on. This is going to be a type called coordinate. And then in parentheses here, you put what the parents of the class are. For today's lecture, the parent of the classes are going to be this thing called object, and object is the very basic type in Python. It is the most basic type in Python.

And it implements things like being able to assign variables. So really, really basic operations that you can do with objects. So your coordinate is therefore going to be an object in Python. All right. So we've told Python we wanted to define an object. So inside the class definition we're going to put attributes. So what are attributes? Attributes are going to be data and procedures that belong to the class, OK? Data are going to be the data representations and procedures are going to be ways that we can interact with the object.

The fact that they belong to the class means that the data and the procedures that we write are only going to work with an object of this type. OK. If you try to use any of the data or the procedures with an object of a different type, you're going to get an error because these data and these attributes will belong to this particular class. So the data attributes is, what is the object, right? What is the data that makes up the object? So for our coordinate example, it's going to be the x and y values for coordinate.

We can decide that can be ints, we can decide that we can let them be floats, but it's going to have one value for the x-coordinate and one value for the y-coordinate. So those are data attributes. And procedure attributes are better known as methods. And you can think of a method as a function. Except that it's a function that only works with this particular type of object. So with a coordinate object, in this case. So the methods are going to define how you can interact with the object.

So in a list, for example, we've said that you can append an item to the end of the list, we can sort a list, things like that. So when you're defining methods, you're defining ways that people can interact with your object. So for example, for a coordinate object, we can say that we can take the distance between two coordinate points. OK? And that's going to be a way that you can interact with two coordinate points. And just to be clear, these are going to belong to this class, which means that if you try to use this distance method on two lists, for example, you're going to get an error. Because this distance method was only defined to work with two coordinate type objects.

All right, so let's carry on and continue implementing our class. So we've written this first line so far, class coordinate object. So now let's define attributes. First thing we're going to define are data attributes. Generally you define data attributes inside this init, and this is underscore, underscore, init, underscore, underscore, and it's a special method or function in a class. And the special method tells Python, when you implement the special method, it tells Python when you first create an object of this type, call this method or call this function.

So how do we do that? So let's implement it. So we say df because it's just a function. The name is the special name, init. And we give it some parameters, right, just like any other function. These last two parameters are x and y, which are going to represent how you create a coordinate object. So you give it a value for the x-coordinate and you give it a value for the y-coordinate.

The self, however, is a little bit trickier. So the self is going to be a parameter when you define this class that represents a particular instance of the class. So we're defining this coordinate object in sort of a general way, right? We don't have a specific instance yet because we haven't created an object yet. But this self is going to be sort of a placeholder for any sort of instance when you create the object. So in the definition of the class, whenever you want to refer to attributes that belong to an instance, you have to use self dot. So this dot notation. And the dot is going to say look for a data attribute x that belongs to this class.

So for methods that belong to the class, the first parameter is always going to be self. It can be named anything you want, but really by convention it's always named self. So try to stick to that. And then any other parameters beyond it are going to be just parameters as you would put in a normal function. OK.

In this particular case, we're going to choose to initialize a coordinate object by two values, one for the x and one for the y. And inside this init method, we're going to have two assignments. The first one says, the x data attribute of a coordinate object. I'm going to assign it to whatever was passed in. And the y data attribute for a particular object is going to be assigned whatever y was passed in.

Questions so far about how to write this init? Yeah, question.

AUDIENCE: [INAUDIBLE]

PROFESSOR: How do you make sure that x and y are inits or floats? So this is something that you could write in the specifications, so the docstring with the triple quotes. So whoever uses the class would then know that if they do something outside the specification, the code might not work as expected. Or you could put in a cert statement inside the definition of the init just to sort of force that. Force that to be true. Great question.

Yeah, question.

AUDIENCE: [INAUDIBLE]

PROFESSOR: Does the x, does this self x and this x have to be the same name. The answer is no. And we're going to see in class exercise that you can have it be different. OK. Great. So this defines the way that we create an object.

So now we have sort of a nice class. It's very simple, but we can start actually creating coordinate objects. So when you create coordinate objects, you're creating instances of the class. So this line here, C is equal to coordinate 3,4, is going to call the init method. It's going to call the init method with x is equal to 3 and y is equal to 4.

I'm just going to go over here and I wrote this previously, because notice when we're creating an object here, we're only giving it two parameters. But in the init method, we have actually three parameters, right? We have these three parameters here, but when we're creating an object, we only give it two parameters. And that's OK because implicitly, Python is going to say self is going to be this object C, so just by default, OK? So when you're creating a coordinate object, you're passing it all the variables except for self.

So this line here is going to call the init and it's going to do every line inside the init. So it's going to create an x data attribute for C, a y data attribute for C, and it's going to assign 3 and 4 to those respectively. This next line here is origin equals coordinate 0, 0 creates another object. OK? It's another coordinate object whose value for x is 0 and whose value for y is 0. So now we have two coordinate objects.

We can access the data attributes using this dot notation and we've seen that before, right? When we've worked with lists we'd say something like, L dot append, right, when we create a list. So the same dot notation can be used with your own objects in order to access data attributes. So here, this is going to print 3 because the x value for object C is 3, and the next line, print origin x is going to print 0 because the x value for the object origin is 0. OK.

So we've created a coordinate object. We have to find the init method so we have a way to create objects when we use the class. And then we can access the data attributes. But that's kind of lame, right, because there isn't anything cool we can do with it. There isn't ways to interact with this object. So let's add some methods. Remember methods are going to be procedural attributes that allow us to interact with our object. Methods are like functions except that there's a couple of differences which you'll see in a moment.

And when you're calling methods, you're using the dot operator, like L dot append, for example, for lists. So let's go back to defining our coordinate class and let's define a method for it. So so far we've defined that part there, class coordinate and an init. So we have that. So in this slide we're going to add this method here. So this method here is going to say I'm going to define a method called distance and I'm going to pass in two parameters.

Remember self, the first parameter, is always going to be the instance of an object that you're going to perform the operation on. So pretty much by convention it's always named self. And then for this particular method, I'm going to give it another parameter, and I can name this whatever I want. I'm naming it other. And this is going to represent the other coordinate object for which I want to find the distance from my self. So here I'm going to just implement the Euclidean distance formula, which is x1 minus x2 squared, plus Y1 minus Y2 squared, and square root of all that. So that's what I'm doing inside here.

Self and other are coordinate objects. Inside this method, I have to refer to the x data attributes of each object if I want to find the difference between the 2x values from them. So that's why I'm doing self dot x here, right. If I just did x, I would be accessing just some variable named x in a program which actually isn't even defined. So you always have to refer when as we're thinking about classes, you always have to refer to whose data attribute do you want to access? In this case, I want to access the x data attribute of my self, and I want to subtract the x data attribute of this other coordinate, square that, same for y, square that, and then add those and take the square root of that.

So notice this method is pretty much like a function, right? You have DF, some name, it takes in parameters. It does some stuff and then it returns a value. The only difference is the fact that you have a self here as the first thing and the fact that you always have to be conscious about whose data attributes you're accessing. So you have to use the dot notation in order to decide whose data attributes you want access. So we've defined the method here, distance.

So this is in the class definition. Now how do we use it? So let's assume that the definition of distance is up here. I didn't include the code. But really all you need to know is what it takes. It takes a self and an other. So when you want to use this method to figure out a distance between two coordinate objects, this is how you do it.

So the first line, I create one coordinate object. Second line, I create another coordinate object. First one is named C, the second one is named 0. These are two separate objects. And I'm going to find the distance. And I want to first call it on one object, so I'm going to say C dot, so I'm using the dot notation to call the method distance on object C. So Python says this object C is of type coordinate. It's going to look up at the class coordinate that you defined. It's going to find this method called distance and then it's going to say what parameters does it take? So it takes another parameter, right, for the other and then, in the parentheses, I just have to give it this other perimeter.

An easier way to see what happens is by looking at what this line here is equivalent to. So the third line here prints C dot distance 0 is equivalent to this one on the right. And this one on the right essentially says, what's the name of the class, dot, dot notation, what's the method you want to call, and then in parentheses you give it all of the variables including self. OK. So in this case you're explicitly telling Python that self is C and other is 0. So this is a little bit easier to understand, like that.

But it's a little cumbersome because you always have to write coordinate dot, coordinate dot, coordinate dot, for every data attribute you might want to access, for every procedural attribute you might want to access. So by convention, it's a lot easier to do the one on the left. And as I mentioned, Python implicitly says, if you're doing the one on the left, you can call this method on a particular object and it's going to look up the type of the object and it's going to essentially convert this on the left to the one on the right.

And this is what you've been using so far. So when you create a list, you say L is equal to 1, 2, and then you say L.append, you know, 3 or whatever. So we've been using this notation on the left pretty much from the beginning of class. So we have a coordinate class, we can create a coordinate object, we can get the distance between two objects.

As you're using the class, if you wanted to use this coordinate class, and you were maybe debugging at some point, a lot of you probably use print as a debug statement, right? And maybe you want to print the value of a coordinate object. So if you create a coordinate object, C is equal to coordinate 3, 4, right? That's what we've done so far. If you print C, you get this funny message.

Very uninformative, right? It basically says, well, C is an object of type coordinate at this memory location in the computer. Which is not what you wanted at all, right? Maybe you wanted to know what the values for x and y were. That would be a lot more informative. So by default, when you create your own type, when you print the object of that type, Python tells you this sort of information which is not what you want. So what you need to do is you need to define your own method that tells Python what to do when you call print on an object of this type.

So this is going to be a special method, just like init is, because it starts and ends with double underscores. And the name of the method is underscore, underscore, str, underscore, underscore. And if you define this method in your class, that tells Python, hey, when you see a print statement that's on an object of type coordinate, call this method, look what it does, and do everything that's inside it. And you can choose to make it do whatever you want inside your definition of str.

In this case, let's say when we print a coordinate object, we're going to print its x and y values surrounded by angle brackets. That seems reasonable, right? So then from now on when you print coordinate objects, you're going to see things like this, which is a lot more informative. So how do we define this? So so far we've defined all that and the last part is going to be new. So we define the init and the distance, and let's define this str.

So underscore, underscore, str, underscore, underscore, is a method. It's only going to take self because you're just calling print on the object itself. There's no other parameters to it. Str has to return a string, and in this particular case, we're going to return the string that's the angle brackets concatenated with the x value of the object, self.x, concatenated with a comma, concatenated with the y value of this particular instance of an object, self.y, and then concatenated with the angle brackets. So now any time you have print on an object of type coordinate, you're going to call this special method str, if it's implemented in your code. Any questions? OK.

So let's try to wrap our head around types and classes because we've seen a lot today. Let's create a coordinate object, assign it 3, 4, as we have been, and assign it to variable C. We've implemented the str method, so when we print C, it's going to print out this nice three comma for our angle brackets. If we print the type of C, this is actually going to give us class main coordinate, which tells us that C is going to be an object that is of type class coordinate.

If we look at coordinate as a class, if we print what coordinate is, coordinate is a class, right? So this is what Python tells us, if we print coordinate, it's a class named coordinate. And if we print the type of a coordinate, well that's just going to be a type. So class is going to be a type. So you're defining the type of an object. If you'd like to figure out whether a particular object is an instance of a particular class, you use this special function called is instance. So if you print is instance C comma coordinate, this is going to print true because C is an object that is of type coordinate.

Couple more words on these special operators. So these special operators allow you to customize your classes which can add some cool functionality to them. So these special operators are going to be things like addition, subtraction, using the equal equal sign, greater than, less than, length and so on and so on. So just like str, if you implement any of these in your classes, this is going to tell Python. So for example, if we've implemented this underscore, underscore, add, underscore, underscore in our class, this is going to tell Python when you use this plus operator between two objects of type coordinate to call this method.

If you have not implemented this method and you try to add two objects of type coordinate, you're going to get an error because Python doesn't actually know right off the bat how to add two coordinate objects, right? You have to tell it how to do that. And you tell it how to do that by implementing this special method. Same with subtract. Same with equals.

So if you want to figure out whether two objects are equal. And when you implement these methods in your own class, you can decide exactly what you want to do. So what happens when you add two coordinate objects? Do you just add the x values, do you just add the y values, do you get them both together, do you do whatever you'd like to do. And then you document what you've decided.

So let's create a fraction object. So we've looked at coordinate, we saw sort of a higher level car object. Let's look at a fraction object. Fraction object is going to be, is going represent a number that's going to be a numerator slash denominator. OK. So that's going to be a fraction object. So the way I've decided to internally represent a fraction object is with two numbers. And I've decided that I will not let them be floats. They have to be integers, hence the assert over here. So inside the init, I've decided I'm going to represent my fracture with two numbers, one for the numerator and one for the denominator.

So when I create a fraction object, I'm going to pass in a numerator and a denominator. And a particular instance is going to have self dot numerator and self dot denominator as its data attributes and I'm assigning those to be whatever's passed into my init. Since I plan on debugging this code maybe possibly sometime in the future, I'm also including an str method and the str method is going to print a nice looking string that's going to represent the numerator, and then a slash, and then the denominator. And then I've also implemented some other special methods.

How do I add two fractions? How do I subtract two fractions? And how do I convert a fraction to a float? The add and subtract are almost the same, so let's look at the add for the moment. How do we add two fractions? We're going to take self, which is the instance of an object that I want to do the add operation on, and we're going to take other, which is the other instance of an object that I want to do the operation on, so the addition, and I'm going to figure out the new top. So the new top of the resulting fraction. So it's my numerator multiplied by the other denominator plus my denominator multiplied by the other numerator and then divided by the multiplication of the two denominators.

So the top is going to be that, the bottom is going to be that. Notice that we're using self dot, right? Once again, we're trying to access the data attributes of each different instance, right, of myself and the other object that I'm working with. So that's why I have to use self dot here. Once I figure out the top and the bottom of the addition, I'm going to return, and here notice I'm returning a fraction object. It's not a number, it's not a float, it's not an integer. It's a new object that is of the exact same type as the class that I'm implementing.

So as it's the same type of object, then on the return value I can do all of the exact same operations that I can do on a regular fraction object. Sub is going to be the same. I'm returning a fraction object. Float is just going to do the division for me, so it's going to take the numerator and then divide it by the denominator, just divide the numbers. And then I'm defining here my own method called inverse. And this is just going to take the inverse of the instance I'm calling this method on. And so it's going to also return a new fraction object that just has the denominator as the top part and the numerator as the bottom part. So then we have some code here. So that's how I implement my fraction object.

So now let's use it and see what it gives us. A is equal to a fraction 1, 4. This is going to be 1 over 4 for a. And b is going to be 3 over four. When I do C, notice I'm using the plus operator between two fraction objects, right? A and b are fraction objects so Python's going to say, OK, is there an underscore, underscore, add, underscore, underscore, method implemented? It is and it's just going to do whatever's inside here. So it's going to say self dot numerator plus other dot denominator. It's going to calculate the top and the bottom. It's going to turn a new fraction object.

So this is going to be 4 plus 12 divided by 16, and 16 over 16. So C as a fraction object is going to be 16 for the numerator and 16 for the denominator because it's a fraction object. If I print C, it should print 16 over 16, so we can even run it, so print 16 over 16. If I print floats C, so this special method float here is going to say, is there a method that converts a fraction to a float and there is. It's this one implemented right here. So it's just going to divide the two numbers, top and bottom, which gives me 1. So it's this one here and here.

Notice I'm doing the exact same method call, except I'm doing it the other way where you type in the name of the class, name of the method, and then what you're calling it on, and this gives the exact same value here, 1.0. And then here I'm calling the method inverse on object B which is going to invert 3 over 4 to be 4 over 3. And then I'm converting it to a float and then I'm printing the value. So it gives me 1.33. So take a look at this code in more detail and see if you can trace through all of those different things and see if you can also write your own new fraction objects. OK.

So last slide. Power of object oriented programming is that you can bundle together objects that are of the exact same type. And all of these objects are going to have the same data representation and the same methods that you can do on them. And ultimately, you're going to be building these layers of abstraction. So you're going to be building on a basic object type in Python, you're going to have integer objects, float objects. On top of those, you can create lists, dictionaries. And on top of those, you can even create your own object types as we saw in this lecture today.