Hello, everybody, and welcome back. So in this video, you're going to learn about object oriented
programming and Python and how you can create your own custom objects by making different classes
in Python. Now, this video is geared towards beginner. So people that have some knowledge of Python
have written Python code before, but I've not yet stepped up into kind of the intermediate stage,
which I'm going to call is this working with classes and objects and are looking to learn that.
So I'm going to be starting completely from the beginning. If you have no knowledge of object
oriented programming, that's totally fine. And this series is meant for you. If you're someone who just
wants refresher, I'd recommend maybe watching the video on two times speed and slowing down when
things get a bit more confusing or when, you know, you need to really learn that topic more.
So let's go ahead and get started. And what we need to do to discuss object oriented programming is
first to find what an object is. Now, a lot of people may think that when we talk about object
oriented programming, that's a completely new thing, you know, you've never seen it in Python before,
and it's a new kind of thing to tackle and to learn. When in reality, you actually see objects
all the time when you're working with Python code, and you just don't know that their objects,
just because they're a little bit disguised, and I'm going to kind of take away that disguise
and show you what all these different objects are. So we may have heard of the notion of type
before. We see this function type. And we know when we ask it what type of certain variable is,
or we ask it what type say, you know, a string is like this, it spits out an answer to us. And
I'll show you what I mean by that. So if I do print type of hello, so we know that hello is a string
data type, let's just see what we get in our console down here. Oops, I didn't mean to do that.
We get class string. So notice that this says class. Now, most people just disregard the fact that
it says this, but this is actually very important. What this is telling us is this right here,
this string that we typed in is actually an object of the class string, right? STR. Now,
what do you think's going to happen when I put in X? Well, we know that X is an integer. So let's
us have a look here. And we can see that we get class int. So notice that these different data
types that we've called them before, a string and an int are actually a part of a class. And what
that means is, even though it doesn't look like we've created an object, when we do something like
X equals one, we've set X is equal to the object, which is a type integer with the value one.
That's exactly what we've done. And the fact that we actually create these kind of objects
is very important in writing Python code. And I know this hopefully shouldn't confuse any of you
too much. But if I write say a function, so, you know, define hello and in here, we just print
hello like that. And what I decided to do is I go, okay, let's look at the type of hello. So
notice I didn't call the function hello with the two brackets. I'm just looking at the actual name
of hello. And we run this. Well, if we add the final bracket at the end here, we get class function.
So pretty much everything in Python that we work with is actually an object of some kind
of class. And later on in the video, what we're going to do is make our own classes so that we
have our own specific types. So these are what are called the built in types. These are built
into the Python language. And that's why they work a little bit differently than other classes
that we'll work with later. But just understand that whatever you create something in Python,
you are really creating an object, which is an instance of a specific class. And that class defines
the way in which that object can interact with other things in our program. And to show you what
I mean by that, let's just look at some common error messages and we'll see exactly what that means.
So we're going to say x equals one, y equals hello. And watch what happens when I try to do something
like print x plus y. Well, we get some error. Let's go up and see here. We get type error unsupported
operand types for plus int and string. So it's saying because our object x isn't int and our
object y is a string, we cannot add them because the program does not know how to work with objects
of those two types. Or in better words, the addition operation is not defined for objects of int
and objects of string at being added together. So it's very important. And pretty much the type
that we have defines what we're allowed to do with the specific variable or with that specific
object. And it denotes the actions that we can perform, plus minus all of that. If I were to change
this right to two and then we just do x plus y, I don't need to print it out. We can see that that's
totally fine. And there's no issue because these are both ints, which means that that operation is
defined for them. Okay. So hopefully that is clear and that makes some sense. I hope that gives you
kind of understanding of what I mean by objects. And now I'm going to talk about methods because
those are something that we can perform on objects. So I'm sure that many of you see in this
before, I'm just going to type string equals hello like that. And many of you know that we can do
something like print string. If I can type this correctly, dot up. Right. So when I write this dot
upper here, what is that actually doing? Well, let's look at here. We obviously know that puts
that all to upper case. But we're allowed to use this what we call method. And this is a method.
Whenever you have this dot operator, you have some name. And then you have the two brackets at the
the end here. And maybe there's some arguments that go inside of there. That is usually a method
that is acting on a specific object. And in this case, what it's doing is we have the method
upper acting on the object of type string that is stored in this string variable. And the reason
we're allowed to use this method on it is because this is a string. Now notice that I can't do
something like, you know, x equals one and then do x dot upper. And the reason I can't do that
is because, well, it's going to say right here, in object, notice in object has no attribute
upper. So these methods, these different operations, the things that we can do with these objects
is based on the type of class that they are. So that's something that we really need to nail in
there. And I hope that makes sense. And now what I'm going to show you is how we can actually create
our own classes because hopefully that gave you a good enough explanation for what objects really
are. So to create our own class, I'm going to kind of do like a template here. And then we'll go
back through and discuss what everything's doing at once. But just try to follow along for now.
So I'm going to say class. And we're just going to do the classic kind of animal example to start
here. So I'm going to say class dot. And we're going to define this initialization, which we'll talk
about later. And actually, no, let's not even do that. Let's just say define, bark like that. And
then what I'm going to do in here is simply print bark. Okay. So what I've actually done here,
and I know this is going to be weird for many of you that have not seen this before,
is created a class called doc. What this means is I'm going to make my own kind of blueprint now
for any objects that are of type dog. And I'm going to start defining the operations that a dog is
able to do. Now in this case, I've created a method. Now a method is essentially just a function that
goes inside of a class. That's the easiest way to define it. And for our basic example right now,
all of our methods are going to start with a parameter called self. And we're going to talk about
what this self parameter means and how all this works later on. But let's kind of build our way
up to that. So I'm going to say D equals dog down here. And what I've done when I did this here
is I'm actually saying, okay, I'm going to have the variable D and I'm going to assign it
to a instance of the class dog. So this is class dog. Again, you don't need to know how this works
right now. We're going to talk about that in a second. But when I write this line here and I put dog,
which is the name of my class. And then two brackets like that, I am instantiating,
right, creating a new instance of the class dog. So D is now going to be an object that is of type
dog. And notice that we don't get any errors there. And when I decide to print out type of D,
we're going to see that we get class underscore main underscore dot. Now the reason we have these
underscores is because this is telling us what module this class was defined in. Now by default,
the module that you run is called the main module. That's why we have these two underscores main
underscore underscore. But we can see that this is again, an object of the class dog. And that
means that whatever we define inside of here is going to be what is allowed or the operations that
can be performed by a dog. And one of those operations is bark. So this is a method. Remember,
we've talked about methods just like dot upper before. So if I want to use that method on
an instance of my dog class, because this is an instance of the class dog, then what I can do
is I can type well, not dot upper, but dot bark like that. So I can call this method on my dog
object again, because it is of class dog. And I have defined a method that works for dogs.
So when I do that, we can see that we get bark. And then it's still obviously continues to print
the type of that object. So this is what we're getting at here. So these are kind of how classes
work. We name the class, typically the convention is to use an uppercase letter. And usually
you go camel case. So you do like dog, like you say, we're going to do two words, you do like dog
hello, right? So camel case like that. And then inside of the method, or inside of the method,
inside of the class, you can define a few different methods and operations that can be performed
by this object. So what I'm going to do is I'm going to say define, I don't know. Let's just say
me out. I know this is wrong because it's not a dog. And we'll talk about self in just a minute,
but I can define another method here. And instead of printing something, I could say return, let's
just say me out like that. So I don't necessarily need to print something. I can return something. I
can have these methods take arguments. So maybe I have me out. And then I put x and maybe what I do
is I return x plus one or something like that. So maybe let's just call this add, call this add
underscore one. That'll be the name of our method. So I can return x plus one. And then what I could do
if I wanted to here is I could say dog, add underscore one, put five there. And let's print out
the value that we get. So let's have a look at this here. And we can see that we get the value
six. So we can make methods that have different arguments associated with them or parameters like
I've put x here. And that just means that when I call that method, I need to pass in a specific value
or specific values so that that can operate and that can work. So that is how the methods work.
So now we're going to talk about itself. But before we can do that, we need to talk about what's
called the init method, which you may have seen me typing at the beginning before I kind of gave
up on x. I didn't want to get that complex that fast. So right now we have two methods. We have
this bark method and we have this add one method. And I hope those make sense on how those work.
And now we're going to talk about this special method here. And by the way, this has two underscores.
So it has underscore underscore and then a knit and then underscore underscore. Now this is a special
method. And what this pretty much allows us to do is instantiate the object right when it is created.
So this method will be called whenever we write this line. So whenever we create a new dog instance
by writing dog and then the two brackets, let me get rid of this down here for right now.
This method will be called and it will pass any arguments that we put in here. So maybe I put
something like Tim in here. It will pass that to this method. So let's say that every time we create a
dog and we want to give it a name immediately. So to create a dog, the criteria is that you must
give it a name. Well, what we would do in here is we would put the parameter name and then here
we would pass in the name. So what we need to do though is what we're passing in this name,
we need to store this in the dog object somewhere, right? We need to have this stored. We need to
be able to access this. This is kind of the nice thing about objects. And this is where we're going
to talk about attributes. What we can do if we want to store this name is we can say self.name
equals name. Now it's important to note that this doesn't, these don't need to match. But what
we've just done here is we've created what's called an attribute of the class dog, which is name.
So what this means is essentially every time we create a new dog object, we will pass a name
through this parameter. So self just stays here to denote the object itself. So like every time
one of these things is called, we will pass a reference to which object it was called on
so that we can access things for each specific object. And we'll talk about that more in a second.
But here what I've done is to find an attribute called name, which is equal to the name that we
passed in. So what I'll do is immediately, I'll just print inside of here the name so that we can see
how this works and notice that when I run this now we get Tim printing out. So even though I didn't
explicitly call this a knit method, because I wrote this line here, it passed this name that I
put in here to the name parameter and then it printed out name here inside of the a knit. And we
also defined an attribute called name that is inside of this dog class. So what self is doing is
every time that any of these methods is called kind of invisibly, you don't get to see it,
the actual reference to this dog object is passed so that we can access attributes that are
specific to each dog. And what I mean by that is I can make another dog. So maybe I say D2,
which is, you know, another instance of the class dog except this time I name it Bill, right? And now
we'll do the print statement again. So we'll say print name just to show how this works.
And here we get Tim and we get Bill. So we actually have two different dog objects now, one name Tim
and one name Bill, and they both store different names inside of their, you know, instance, if that's
what you want to call it. So let's just show how this attribute works. So the point of this attribute
is that it stored permanently for each specific object. So I can actually go ahead and go down here
in print d dot name after that's defined. And I can print d2 dot name like that. And when we look
here, we can see that those names get printed out. So Tim and Bill and notice that I remove the
print statement from up here. So these attributes when we do something like self dot and then
anything that we want we can call whatever we want is equal to some value, we can reference them
later on and we can reference them from methods inside of our class. So an example about is
something like say define get underscore name. So the first argument here is always going to be self
the first parameter. And the reason for that is because we need to invisibly pass the actual,
you know, dog object itself so that we know which dog we're accessing when we're going to get
say the name of the dog. So here we're going to say define get name and all we're going to do
is return self dot name like that. So now rather than say saying dot name, we can call dot get underscore
name, which is a method, right? And we can do that down here as well, get dot get underscore name.
And then we see that we get Tim and we get Bill. So that is the basics behind objects. Now of course,
I can make more attributes as well. So I could do something like age. So let's say my dogs,
I want them to have a name. So let's say my dogs want them to have a name and an age. Well,
what we'll do now is I'll say, okay, so in our initialization for dog, we're going to take a name
and we're going to take an age. So we'll define the attributes in here. So self dot age is equal to
whatever age we passed in. And now every time that I make a new dog object, I need to pass a name
and an age. So these, this Tim will be an old dog here, Bill will be a young one and we'll run
this and we see that we get no issue. And if I wanted to, I can go ahead and define another
method here. So get age and we'll return self dot age like that. And then if we change this to get
age and we change this one down here to get age as well, what is the issue here? Oops, so I'm
forgot to put myself in here. So notice what happened when I forgot to put myself. So that's
actually a decent error to run into. I didn't put self in here as a parameter and you can see I know
that's kind of messy here. It says get age takes zero positional arguments, but one we're given.
So what does that actually mean? So it's saying it takes your arguments, which it did when I
didn't have self here, but one was given, but I didn't give any arguments inside of the brackets.
Well, that's because when we call a method, this right, the actual dog object itself immediately
kind of invisibly gets passed to that method as the self parameter. So we know which dog it's
talking about. So now if I go ahead and I add self, we can see that this works fine and we get
34 and we get 12. So that's an important thing to remember. Now what we can actually do is we can
other methods that modify these attributes or create new attributes. So I can do something like
for example, set age. So I can say define set age will give itself, of course, and then we'll put
an age here. And what I can do now is say self, age is equal to age. And now what I'm going to do
for Tim and we'll get rid of bill for now because I think we get the point on how it works for
different objects is I'm going to say d dot set underscore age and I'm going to change his age to 23.
And when we printed out, we can see that 23 actually gets printed out. So we can modify and we
can access these different attributes from methods inside our class. And this is where things get
things get very powerful because this allows us to access data that is stored within a specific
object and do different things with it based on how different methods and different things are
being called. Right? And this just is pretty much the blueprint that defines how a dog actually
works, how it operates, what it can do, the methods associated with it and the attributes that exist.
Now, some of you may be saying, well, why do I need to do this? Right? Like this seems
kind of redundant. I could write in a different style. Well, the next thing about object
oriented programming is once you create one of these classes, you can have an infinite amount of
instances of this class without having to change anything. So let's say, for example, that we'll
leave that class up here right now, but we want to simulate what we've just done here for the
Tim dog, right? We want to have an age and we want to have a name and we want to be able to change
his age and all that. Well, many of you that were more beginner programmers would probably tell me
we can do something like this. We can say dog one underscore a name equals Tim. You could say dog one
underscore age equals, you know, 34 and there you go. You've just defined these two attributes.
If you want to get it, you can just access the variable. If you want to change it, you can just
change the variable. Okay, nice. But what happens when I want to make 25,000 dogs or every time I run
my code, I want to make a different amount of dogs. You can't find a way that you can write all
these different variables that have one, two, three all the way up to 50,000 or have many dogs I
have to represent that. That's why we use objects is whenever we're going to be kind of reusing
something. There is some instances in which we make a class where that we're only going to use once
or instantiate once, but those are kind of more complex examples that we won't get into here.
But then okay, so some of you might tell me, all right, well, Tim, I can just do list. I can say
dogs equals that. I can say dogs underscore name equals that, right? And here we can put,
well, the first name is Tim and then we had fill. So this will handle the idea that
we can't just have all these different variables, right? So dogs age equals, and then we go,
let's say, I don't know, 32, 14. Okay, that's great. But the issue with this is that it's really
a pain when I want to access the dogs age, the dogs name. And then what if I had like 25 other
attributes or methods associated with the dog, this is just just a pain. You don't want to deal with
that because I have to now find the index of whatever dog I want in one list, which is a very time
consuming operation and computing. And then I need to reference that index and all the other lists
for all the other attributes. And it just becomes very messy very quickly. And let's say I want to
delete one instance, like the, um, you know, the dog object bill or something like that, right?
Then I need to find the index of this. I need to find the index of all of the attributes in
and all the other lists. And I need to delete them at the same time to make sure that all my data
stays consistent. And there's no kind of offsetting things or too many attributes in one list.
So it just does a pain to do it like that. So I hope this makes sense on why we would do object
oriented style. And now what we'll do is go into one example where we create a more complex object
and show kind of the advantage of that. Okay, so a lot of people typically will stomp at kind of
the previous example that I just gave you. Now I want to go a little bit further and show you
the advantage of doing multiple classes. So rather than just using one class, I want to show you
how different classes can kind of interact in an example where I have a bunch of students,
these students all have some great assigned to them. And they're all a part of a course.
And then that course will have some methods on it to do things like find the maximum grade
of all students, give us the average grade of all students, tell us the lowest grade.
Some things that you may actually want to do, say if you were trying to model or create a system
for a school or something like that, right? So this is obviously going to be a little bit more of
an example as opposed to super practical, but hopefully this should give you some more insight
into how we would do something like that. So I'm going to start by creating a class called student.
I'm going to go a little bit faster here, but I will slow down and talk about what I've done
after so don't worry if you can't keep up. So we're going to have a student and each student
is going to have a name, age, and a grade. So we're just going to say self.name equals name self.age
equals age, and then self.grade equals grade. Now this numeric grade is going to be between 0
and 100. So I'll just write that down here just so we know that. And that should be good for
our students. So we could of course go in here and add some methods if we wanted something like
get underscore grade. And we'll just add one just so we have it in here. And what we can do is return
self.grade like that. Okay. So this is this is where we're going to leave our student.
Now we could add a lot more things to this. We could add a change grade. We could add a test. We
could have weightings. We could do all kinds of crazy stuff. But for now we're going to leave it at
that. So again, we've defined three attributes here. We have a name and age and a grade. Those are
equal to the name, age and grade that we pass in when we initially create a student. And then we have
one method. Remember this is a method that simply returns a student's grade. Now I'm going to
make a new class called course. Now what I'm going to do in this course class is I'm going to have
the ability to add students to a course. So when we create a new course, what we're going to
need to do is we're going to need to define the name of the course and the max underscore students
that can be enrolled. So here we'll say self.name equals name and self.max underscore students
equals max underscore students like that. All right. So what we're going to do now is we're going to
add a method that will allow us to actually add students to this course. But how are we going to do
that? How are we going to have students stored inside of a course object? Well, what we can actually do
is we can make a list of students. So I'm going to say self dot students equals a blank list.
Now notice that I've made an attribute and I didn't assign it directly to one of these things that
was passed into one of these parameters arguments, whatever you'd like to call them. That is totally
fine. In fact, a lot of times we'll create attributes like self dot is active, something like that
equals false. Right. We can do that. That's totally fine. An attributes are just whatever we decide to
define. And if we want to assign them to say the argument or parameter that's here, that's fine.
And we can do that. So there we go. We have self dot students, which is a blank list. And I'm going
to start by creating a method here that's going to allow us to add a student object into this list.
So I'm going to say define add underscore student like that. And in here, we're going to take
self and we're actually going to take student. Now this student right here is actually going to
be an instance of a student object. And I'll show you what I mean by then the second. But all we're
going to do is say if the length of self dot students is less than and in this case self dot max
underscore students, then what we'll do is we'll say self dot students dot append student. So
we're going to create a list of students inside of this, you know, this list right here. Right.
That's what we're going to do. And we're going to add them in only if this is less than the maximum
students in the class. Now what I'll do is I'll actually return true if the student was added
successfully. And then down here, I'll return false if not so that we can tell maybe if we make a
program down below if that student was added properly or not. Now I'm going to add another method
and here I'm going to say define get average. Great. Now we'll code that one out in a second. But
let's do an example of how we can actually now add students to these classes. I'll make that a bit
smaller so we can read it. So let's make a few different students. So let's say s1 equals
student. What do we need to pass for student and name agent grades? So let's call that Tim. Let's
call that 19. And let's call that 95. Tim's a smart guy. All right. And then we'll do s2 equals
student. We're going to say bill. We'll put him at 19 as well. And he's not as smart. He is 75.
And then let's do s3 equals and let's go Jill keep with the trend Tim build Jill 19 and maybe
she has a 65. Okay. So we've created three students here. And these students are proper. They
should work fine. Let's run this code and make sure everything's all right. Now how can we actually
add them to our course? Well, the first thing we need to do is make our course. So we're going to say
course equals course like that. What do we need when we make a course? We need a name and we need a
maximum amount of students. So let's name this course. Let's say science or something like that.
And let's say the maximum amount of students is actually going to be two because I want to show
what happens when we add a student pass the maximum. Now we're going to add students. So how do we
add them? Well, we have to call that method. So course dot add students. So there we go. We can add
student s1. And then let's do course dot add student again. And let's add s2. So now let's run this.
We see everything works fine. And let's actually make a thing that can, you know, print out some
thing about our student or can show our students. So what I could do is I could say let's print down
here course dot and this students. And let's have a look here. And we get main dot student
object at some gibberish location, main student object at some gibberish location. So what this is
actually telling us is both of these things inside our list right now are student objects.
And notice what I can actually do is let's say index the zero with item in that list. So the first
student that we added. And I decide to call dot name on it. Well, then that should hopefully print
out the name of that first student, which is 10. So that's cool. And that's how we do that, right?
We can add things into this course. So now this course stores all of our students. And since all
of these students have a great on them inside of our course, we'll be able to access that.
So now that we've done that, let's write this method that can get the average grade of all of the
students that are enrolled in the course. To do that, what we're going to need to do is grab
all of the students from the students list. We're going to need to add that to an average.
And then we're going to need to divide that value to figure out what that is. So what we'll do is
we'll say value equals zero. We're going to say for student in in this case self dot students,
then we're going to say value plus equals student dot gets grade like that. Now notice that we
could just type grade. If we wanted to, but I usually just like to use the method because say we
we were to change any attribute later, then this code wouldn't break. So long as this method was
still name the same thing. And we can modify the method in here. So they return the proper grade.
Like say we had a student enrolled in multiple courses. Then we could, you know, determine their grade
in a different way. And when we call get grade, maybe we don't just return self dot grade. We return
something different. So this still continues to work. So we'll say value plus equals student dot
grade. And now all we'll simply do is return in this case value divided by the length of self
dot students. So let's see what the average grade of our courses. So course dot get average grade
we'll actually current that out like that and run that. And we can see that the average grade is
actually 85. And that makes sense with the grades that we have here. So that's the idea behind
what I'm trying to show you is that we have, you know, we can have different classes. We can have
attributes with them. And when we can program an object oriented style, now it doesn't matter
how many students we have or how many different courses we have and what students are enrolled in
which in fact, what we could do is we could make another course, we could add the same students to it.
And then we would obviously have to change the way that their grade was calculated. But that's
something that we could do. Now let's just look at what happens when I decide to try to add that
third student. And so course dot add student like that. And we decide to add student S3.
We can see that we get the value false. And notice that the average grade does not change
because we didn't actually add that into the course, right? Okay, so we finished off the basics
on classes and objects and how to create our own classes. And hopefully that last example helped
you to really kind of understand the advantage of doing this. Now what we're going to talk about
here is something called inheritance. Now this is where we slowly started getting to a little bit
more complex and some more difficult concepts. So try to follow along, but I don't find
inheritance is that extremely difficult. So the idea behind inheritance and we'll show what that
is in a second is you have two classes that are very similar. So let's say we have a general class
called maybe or let's actually let's do a better example. Let's say we have a class called dog and
a class called cat. And in fact, let's actually code these out so that we can see this for real.
So let's say in the init method of the cat, what we're going to do is we're going to have
self name and age like that. So we're going to say, you know, self dot name equals if I can type
properly, which apparently is not happening right now. And then self dot age. And then we'll do
define speak and all we'll do in here is just print, um, you know, what does this cat meow? Okay,
and then let's say we have a dog. So we'll say class dog like that. And then we'll define
underscore net underscore underscore put the self, put the name, put the age because that's
all we want for that. And we'll say self dot name equals name self dot age equals age. And then
define here speak. And the only difference between these two classes is actually going to be the fact
that this prince bark instead of meow. So notice that these two classes are almost identical.
In fact, there's only one line of code that's different other than the class definition at the top.
So there must be a way that we don't need to write this twice that we can actually use what's
called inheritance so that these dog and these cat classes can well inherit from an upper level
class, which means that all that functionality is defined in one place. And we only need to write
what's different about those two classes inside of them. So ideally what I would like to have
is to be able to remove this and knit class from both of these and just have the speak method
because the only thing that's specific to a cat in a dog, at least for my example is the fact that
one of them says meow and one of them says bark. So let's go show how we can actually do that.
So by removing that, you know, was a knit method and just having these methods here, what we
need to do is make an upper level class, which I'm actually going to call pet. So I'm going to say
class pet. And what I'm going to do in here is define that a knit method that we had before.
I'm actually going to define a, let's say show. So this show method is just going to show me
all the things about my object. So I'm going to say print. And in here we're going to say,
you know, I'm actually going to use an F string. You may not know what that is, but don't worry about
it. I am self dot name and I am self dot age years old. Okay. So what I've done is I've defined
this pet class. And this pet class essentially is going to contain the functionality that I want
the cat class and the dog class both to have. And then inside of the cat class and inside of the
dog class, what I'll do is I'll define the methods or the attributes or whatever it is that I
need to do that are going to be different for this specific class. So notice that pet is general.
We call this a generalization, whereas cat and dog are more specific. So how do I actually allow
the cat class and the dog class to use this functionality? Well, what I can do is simply add brackets
and write pet. Now what this stands for is I am inheriting the upper level class pet. So we're saying
this is the general class. This is a more specific class that's being created and inherits from pet
and same thing with dog. Let me show you how this works. So let's create an instance. First of all,
of the pet class. And then we'll make one of the cat and the dog class. And I'll show you how it
it works. So let's say p equals pet. Notice that for pet we need a name and we need an age. So
let's say it's him. Let's put him at 19. And then let's do p dot show. So let's look at this here.
I am Tim and I am 19 years old. So that is how the pet class works. Pretty straightforward.
Now let's make a cat class. So let's say c. I'm going to say c equals cat. I'm going to say
the cat's name will be bill. And that cat will be 34 like that. And then let's do the same thing
here c dot show. Now notice that even though there's no method called show inside of cat,
it still pops up and says I am bill and I'm 34 years old. That is because it inherits the properties
from the pet class because I've defined that up here. And notice that even though I didn't
find an init method in here, this still worked fine. Right? There's, you know, this was okay.
We initialized because we use the init method that was defined inside of pet. And of course,
we can do the same thing with dog. So we can say d equals dog. What do we call this one before
jail? 25. And let's do d dot show like that. And we go, I am jail and I'm 25 years old. So that
works. But now let's show what happens when we call speak on the cat and the dog. So if I decide
to call speak like that and we'll call it on both of them here, we can see that we get me out
and we get bark. And again, that's because the speak method is different for the cat class
and different for the dog class. And since it's defined inside of here and we created an instance of cat,
when we make a cat instance, well, we're going to use the speak methods that's defined here.
And in fact, what I could actually do is define speak up here. And I could say define speak and I
could say print, I don't know what I say like that. Okay. And then if I decide to change this
from show to speak, notice that we're calling speak three times. Speak is defined here and it's
defined in both of our child classes is what we call them. So when they, when this is the upper
level class, the more general version, any classes that inherit from it are known as the child
classes or the derived classes. That's not that important to know, but just, you know, figured
out through that lingo out there. And notice that when I run this, we get, I don't know what to say,
me out and bark. So if there is a method defined in the lower level class or the child class,
that is the same name as the upper level class, it will automatically override that method.
So it will take over anything that's defined in here is more specific to this class. So it will
use that rather than using this upper one, right? And you might ask, well, why would we even
bother defining one in here if we were just going to take it over later? Well, we might create
another pet, right? Maybe like a fish or something, for example, like I can do something like this,
class fish, pet, like that. I can literally just define that and put pass inside of here.
And now what I can do is say, okay, let's say f equals fish. And then we need a name. So let's
call this bubbles, why not give it 10. And now we can say f dot speak. And notice that we get,
I don't know what to say for this fish class because there was no speak defined inside of here,
it used the speak that was defined in the upper level or parent class, right? The one that we
inherited from. So that is the basis behind inheritance. Now it gets a little bit more complicated.
I'm going to show you the more complex aspects because let's say I want to add one attribute to my
cat. So let's say that for cats, we actually care what color those cats are as well.
Well, what I need to do to do that is change this initialization method, right? Because I want to pass
that in when I create a cat, but I don't necessarily want to rewrite this whole thing. So what I'm
going to do, and I'm kind of need to rewrite the whole thing, but you'll see why we would actually
do this in a second. I'm going to say self name, age, color like that. And now all I'm going to do
let me say self dot color equals color. So what some of you may say here now that we've defined
the color in here is that what we should do is take this name and age, right? Just copy it
and paste it in here. Now that would be a correct answer, but I'm going to tell you why we shouldn't do
that. So the idea here is that sometimes in the initialization method of our parent, other things are
happening other than just redefining attributes, right? And in that instance, it would not be correct
for us to simply omit the fact that we're not going to call this a net from the parent. We're
just going to define the attributes because that could mean that we miss out on a very important
you know, function that's happening from inside of this initialization. To give you an example,
like saying some web applications, maybe this initialization actually calls a database, right?
And then ask for some information for a database and it sets up the object using that.
It wouldn't necessarily be correct then for me to just, you know, take the attributes that we're in
here and just redefine them as attributes here. I would actually still need to call that initialization
to make sure the object was set up properly. So to ensure that that happens when we do an inheritance
like this, we do need to define the arguments that we need or the parameters that we need for the
parent's initialization. So name, age, but there's a fancy way to call that. So rather than, you know,
rewriting this here, I can actually explicitly call this and set up our object that way. So to do
that, I'm going to say self or super dot underscore underscore net done underscore underscore name
age. Now what this is saying is super stands for reference the super class and the super class
is actually the pet class or the class that we inherit from here. So that's what super stands for,
the class that we have inherited from. Then underscore underscore net underscore underscore defines
the method that we want to call, right? And then name and age are the arguments that we're going
to pass to that. So name and age. Notice I don't need to pass self. That's fine. We don't need self.
And it's going to call that. And what's going to happen is it's going to run whatever's inside
this initialization. Then that's going to set up the name and the age for our object. So we will have
those properties to find. And then we will call self dot color equals color. We will execute that
line. So now we have the color. Right. And if I just go ahead and wanted to find here show,
so I want to change the show method maybe for this cat object, then what I can do is I say,
I am self dot name and I am self dot age and I am. And in this case, self dot color, right. So we
can change the show method in here. And now let's go to cat. Let's change this to show. Let's
add a color because we need to add a color. Now we have to find that in the in it. And let's run
this and see what the issue is now that we're getting fish is not defined. Oh, did I? I guess I've
deleted fish or we got rid of some point and I did not remember. Okay. So anyways, let's run this.
And we can see that we have I don't know what to say. I'm Bill and I'm 34 years old and I'm brown
and then we get bark. Right. So that is how that works for inheritance. And this is how we call
the super class or the upper level parent class. Right. We need to call this initialization method
before we just go ahead and do anything else because that parent's initialization may be important
and may do other things. It may call another method. Right. So we can't just simply skip that.
We need to call that explicitly by writing this line. Now this line is kind of complicated syntax.
Um, you know, it's easy to forget, but try to remember it super again references the parent class
and then we have a knit. Um, and like that. So I hope that gives you an idea on how inheritance works.
Now it's hard to give really good inheritance examples without going more complex and more into
detail. So I'm going to omit doing that for now, but just remember that when you have classes that
do a very similar thing, they have almost everything identical except maybe a few attributes or a few
methods. It might be a good idea to what we call generalize and make a parent class that is a
general class that defines functionality that will be used in all of your child classes.
And that is a very common practice and object oriented programming to use inheritance. For example,
a very good example is, for example, a good example is something like a person hierarchy.
So let's say you have an, you're working in an organization. And the example we want to consider
is we have managers and we have employees. Now managers have different access than employees,
but employees and managers have very similar properties. They'll have a name, they'll have an
age, they'll have an ID, they have a birth date, they have many different things that they are the
same for both of them. Well, if we were trying to model that system and we were going to program that
and make that, what we would likely do is create an upper level general class called person that
defines all of the attributes and all of the methods that are general to all people, whether they
are managers or employees. And then we would create two child classes, one for employee and one for
manager and that would define the specific things that the manager can do and that the employee can
do that are different from each other. So that's the idea behind inheritance. Hopefully that makes
sense. And now let's move on to our next topic. Okay, so now it's time to talk about static and
class methods and class attributes. And in fact, we're actually going to start with class attributes.
Now previously, you would have seen that every time we defined an attribute for one of our objects,
we used self, right? And inside the class, we had self everywhere, self was referring to the
instance in which we were talking about in that context, right? So here, what we're going to do
now is talk about class attributes. Now class attributes are attributes that are specific to the
class, not to an instance or an object of that class. So I'm going to do a basic example where I
just create a class person. And I'm going to say number of people equals zero. Now in here, I'm going
to define a net method and say to find a net like that. And we're just going to say each person
person will simply have a name, keep it nice and simple, like that. So self, that name goes name.
Now let's make P1 person. We'll talk about what I've done in a second here in case anyone's confused.
And let's say P2 equals person. Let's make this gel. Okay, so we have this number of people
thing. And I'm sure a lot of you are like, what the heck is that? Well, this is a class attribute.
And the reason that's not a regular attribute is because it does not use self. So because it's not
defined inside any method, because it does not have access to an instance of the class,
it is defined for the entire class, which means that this is not specific again to any instance.
It's not going to change with from person to person, whereas we know something like self.name
will be different for each instance of the person class. This is not different for each instance
of the person class. In fact, it's the same. So what I can show you is that I can actually go ahead
and print say, you know, P1 dot number of people. And that gives me the value zero. But what I can
also do because this is not specific to the instance of any class is right person dot number of people.
And the reason I can write person is because again, this is specific to the class, not to the instance.
So we can access it by just using the name of the class. And that actually means that I can
change it using the name of the class as well. So person dot number of people equals eight,
right? And then if I go ahead and say, okay, P2 dot number of people notice that we get eight,
even though I didn't explicitly change it on P2, it changed for P2 because this was specific to the
class. And when I reference this, all this says is when I say P2 dot number of people,
the way that Python interprets this is, what is the type of P2? Okay, that's person. Notice,
you can see it's popping up here. It's this person. Then let's say does it have an attribute called
number of people. No, this person itself does not. Does the class have an attribute called number
of people? Yes, it does. Let's display that. And since we changed that to eight, that's why we're
getting that value. And of course, if we do this for P1, like we've shown, right, we'll get the same
number. We'll get P1 dot number of people get eight. And if we decide to change this halfway through,
right, so we do a person equals nine, obviously that will become nine now just because we changed
it right before we printed the next value. So that is the basics behind class attributes.
Now, there's a lot of different uses for them. Now, in this case, what I want to do is have a
number of people. So what I was going to do is inside of here, say person dot number of people,
plus equals one, so that we keep track of how many people or how many instances we have created
of this class person. So now, if I decide to print P, you know, person dot number of people,
and we'll do that after we create the second person as well. See, we get one, two, and this
automatically increments it. So that's a basic example of when you would use a class attribute.
They're not extremely useful, but, you know, it is something that you may want to consider.
And sometimes say you want to define a constant something like maybe gravity or something that's going
to apply to every single person that you want to be a constant value, then you define that as a
class attribute. So that if you ever decide to take this class and use it somewhere else,
that constant is still defined as opposed to putting it up here. Like as opposed to saying gravity
up here and making it equal to negative 9.8 meters per second at the top, what you would do
is you would make that a constant inside as a class attribute so that now every time you want to
access the gravity property for, you know, a person, what you would do is you can reference directly
the person's class constant of gravity rather than a global constant, which may not be there if you
put this class in a different file. And that's the idea behind these classes as well as that they
are exportable. I can write a class in one file and I can take it and move it to another file,
and hopefully it should continue to work, assuming it does not depend on anything from the previous
file. So ideally you want to make your classes as robust as possible, which means that they don't
need anything outside of their initial class definition unless that's going to be another class
that maybe it's interacting with, like in the example before where we had course and we had
student. But that is the idea behind class attributes. Now, let's talk about class methods.
So class methods are defined a little bit differently than regular methods. And in fact,
I'll show you how they work. We have a good example kind of setup here. So I'm going to say
define. And in this case, we're going to say number of people like that instead of saying
actually self, we're going to say CLS. Then what we're going to do here is return CLS dot number of
people. And we're going to use what's called a decorator to denote that this specific method is a
class method. And to do that, we write at class method directly above the function. All right,
yeah, I guess function method, whatever you want to call. So I know this seems a little bit weird,
but the idea behind this is this method here is not going to be acting on behalf of one instance.
It's not going to be specific to an instance. And in fact, you can call it on instance if you want,
but that's not really going to be very effective. What this is meant to do is be called on the class
itself so that it can deal with something like, you know, returning the number of people that are
associated with this class. So these are class methods. That means they act on the class itself,
they do not have access to any instance. And that's why I've written CLS here instead of self
because there's no object. What it's doing is just acting on this class. So for example,
say we wanted to add to the number of people. Then what I could say is class method
define add person like that CLS and would say CLS dot what is it number of people like that
plus equals one. So that would be these are class methods. We denote them with at class method.
Just so we know that they're not referencing, you know, self like that. They're referencing the
class. So let's actually have a look at how we use that now. So you don't need to necessarily print
the number of people anymore. What we can do is we can go down here. We can say person dot number of
people. And I don't know why there's so many brackets showing up there. But if we look at this,
what we would need to print it out first, we should hopefully get the value of two. So let's look
okay. So we need to rename this so that this is not the same as the attribute because that's going
to be confusing. So let's add an underscore there because I was realizing what that was going on
and we can see that we get zero. Oh, so that is because I did not continue to add here. So actually,
what I'm going to do is, and this actually be a good example. So let's illustrate this here,
is I wanted to say two, but I forgot that I forgot, you know, didn't continue adding this.
But what I can do is actually use the class method that I've defined here to add a person.
So I can say person dot add person like that inside of my nits. And what that will do is
call the class method on the class person. And then it will add to the number of people. So now
when we run that we get two. So that is how a class method works. I don't need access to the
instance to call it. Although I can use an instance to call it if I want, I can simply reference
the actual name of the class. And this does not access any specific instance. It only accesses
these class attributes or anything specific to the class itself. Okay, so that has been class methods.
Now let's get rid of those. And let's talk about static methods. So sometimes,
I'm actually going to delete this entire thing. You want to create classes that kind of
organize functions together. So for example, you know, when you say like import math like that,
and then you get access to all these math functions like math dot ABS or math dot square roots
or whatever it is you're going to use. Well, what they sometimes end up doing in an object
oriented programming, this is pretty common, is when you have a bunch of functions that you
normally just define, like you define like add one like this, you know, I would do something,
you define add to like this. Well, what you want to do is you want to actually organize them into
a class. And the reason you do that is just so it stays a little bit structured. You can move
all those classes together to another module and continue to use them. And to do something like
this, you want to use what's called a static method. Now, let's make a class called math.
And what I'm going to do in here is I'm going to define some methods or some functions that
I'd like to be able to use, but that are not specific to an instance. So I don't want to have to
make an instance of this math class to be able to use these methods. I want to be able to call them
at any points. And it doesn't matter if I have an instance of the math math class or not,
I would like to be able to use them. So what I'm going to do is actually create what's called
a static method. Now static means not changing, right? It means staying the same. And that is a
really good way to describe what these methods do because they do not have access to an instance,
just like the class method. All they do is something. They do something, but they don't change anything.
And that's the idea behind a static method. They don't change anything because they can't. They don't
have access to anything. So in here, what we're going to do is just say define ad five. And in here,
I don't even need to put a cell for a CLS because this is not going to access anything. All
it's going to do is just act as a function that is defined inside of this class. And again,
some of you, I'm sure are yelling saying, what's the point of doing that? Why don't I just
find a function globally? Well, it's more of an organizational thing. And there's some more
specific applications in which you would use a static method. So I do need to show them to you.
So here we're going to take a number and all we're going to do in this method is return x plus five.
So now if I want to actually use this, what I can do is I don't need to say like m equals math,
like that, and make an instance. That's not necessary. I can simply write the name of the class
and say math dot add five. Let's put five in here. Let's have a look if I got rid of the s and we
get the value 10. So this is called a static method. And we can make as many of these as we would
like just as we can make as many class methods as we want. So maybe we do add 10, right? And then we have
that like that. And we can change this method to say add 10. And now if we run this, we get 15.
So that is a static method. Notice it doesn't need anything. In fact, what I can actually do is say
define PR. Let's say that's going to stand for print. Let's make this an static method. And now
let's just print run. Like I'm just showing that you that you don't need any attributes in there
or arguments. And let's just call PR. And now you can see that we get run. And since I printed the
value of that, it printed out none. But there we go. Like that. Okay. So that is static methods
and class methods. And to be honest with that, that is pretty much everything you need to know about
classes and objects at least at a beginner level. Now, there's some more interesting things that we
could talk about. But in the idea of keeping this more for beginners and so that everyone can kind of
understand doesn't get too confused. I'm never afraid from discussing anything further. But I hope
this really gave you a fundamental knowledge of how classes and objects work in Python. A little
recap here is to remember that everything we work with is an object in some sense. We have functions
which are objects. We have strings which are objects, integers are objects. And what an object does
is it's an instance of some class. And that class defines the properties and almost kind of the
blueprint for that object. It says, okay, so if we have a string, we can use the method like dot
upper dot lower. If we have an int, we can add integers together. And a class, the type of an object
is very important because it defines the behavior in which it can exhibit. So that has been
classes and objects in Python and introduction to object or into programming. I hope you guys
enjoyed. If you did, please make sure you have a like. These videos are not that easy to make and they
are definitely time consuming. So I would appreciate it. Subscribe to the channel. And of course,
let me know if you have any questions or if there's anything you would like to see in the future.
Introduction to Object Oriented Programming
Understanding Objects in Python
Built-in Types and Class Instances
Creating Custom Classes
Why Use Classes in Python?
Creating Student Class in Python
Understanding Course Class
Adding Students to a Course
Inheritance in Python Classes
The Importance of the Super Function
Understanding Class Attributes
Defining Class Methods
Using Static Methods in Python
Defining Functions within Classes
Understanding Static vs Instance Methods
Recap on Python Classes and Objects
How do built-in types relate to objects in Python?
What does the 'self' keyword do in object methods?
What happens when I forget to use 'self' in a method?
How do objects simplify managing many instances like students?
What advantages do classes offer when handling data consistency?
How does inheritance help in avoiding code duplication?
What is the role of the super() function in inheritance?
How do class attributes differ from instance attributes?
What can static methods do in class design?
How do static methods differ from instance methods in Python?
Why would you define functions inside a class instead of globally?
What is the significance of everything being an object in Python?
The video starts by defining what an object is in Python. The host explains that objects are instances of classes, and that every object has its own set of attributes, or properties, that define its behavior. The host then provides examples of how objects are used in Python code, such as creating custom objects with specific attributes. He also discusses the concept of inheritance, where a subclass can inherit the properties and methods of a superclass. Throughout the video, the host emphasizes the importance of understanding OOP when working with Python code, as it is a fundamental aspect of programming in this language. The video ends by encouraging viewers to practice creating their own custom objects and classes.