It's important for software developers to
understand object oriented programming. In this course, Jim from JimShapeCoding will teach
you all about object oriented programming and Python object oriented programming, it could be
what is holding you back from being a great Python developer. And as well as lending your first job
as a software engineer, Welcome everyone to Python object oriented programming course. Now if you
struggle to understand the concepts of object oriented programming in the past, then you are
totally fine. And you're in good hands. Because in this course, I'm going to make sure that this
will be the last tutorial that you will ever watch about classes and the complex concepts that
comes with object oriented programming. And we are going to do this by developing a real Python
application, that is going to be very cool to write. And we will add to its complexity step by
step. And throughout the way, we will understand everything that we need to know about object
oriented programming. Now there are going to be some requirements to this course, I do expect
from everybody to know at least about functions, variables, if statements and as well as for
loops. And if you know those things from other programming languages, then this
is also fine. So with that being said, let's get started. Now to explain why you
should write object oriented programs, I will explain the concepts based on a store
management system that we will start developing together. So starting to think about how to
take our first steps with such a problem, we could first think about tracking after the
items that we have right now in our store. So one way we could get started, we could create those
four variables to start tracking after our items. So as you can see, we have our first variable item
one equals to phone. And then we have three more variables that are intentionally starting with the
prefix of item one, so that we could describe that those four variables are related to each other
by following the correct naming conventions. Now, you might think that those four variables are
related to each other only because it uses the same prefix of item one. For Python, those are
just four variables with different data types. So if we were to print the type, for each of those
four variables, now, we will receive their types with no surprises, right, we will receive string,
and integer for price, quantity and price total. Now I want to focus on those specific outputs
right now, because as you can see, for each of the types, we also see the key word of class. Now this
means that those data types are actually instances of strings or integers. So in Python programming
language, each data type is an object that has been instantiated earlier by some
class. And for the item, one variable that has been instantiated from a string type of class.
And for the price quantity and price total, those have been instantiated from a class that
is named Iand, meaning integer. So it could have been nicer. If we call the tail Python
that we want to create a datatype of our own, it will allow us to write a code that we can
reuse in the future easily if needed. Now, each instance could have attributes to
describe related information about it. And we can think about at least some good
candidates for attributes we could have for our item datatype, like its name, price, or quantity.
Alright, so let's go ahead and start creating our first class. So I will clean everything from here,
and we'll go ahead with it. So it is going to be divided into two parts, the first one will be the
creation of the class. And the second one will be the part that I will instantiate some objects of
this class. Now when I say creating an instance, or creating an object, basically I mean to the
same thing, so you might hear me saying one of those. Alright, so let's go ahead and say class.
And then this needs to be followed by the name of the class that you want to create. So we would
like to give it the name of item. And then inside of this class, in the future, we are going to
write some code that will be very beneficial and very useful for us. So we won't repeat ourselves
every time that we like to take similar actions. But for now, temporarily, I'm going to say here a
pass so we will not receive any arrows inside this class definition. Alright, so now that we have
created our class, then we are allowed to create some instances of this class. So let's go ahead
and say item one is equal to item. And that action is equivalent to creating an instance of a class,
just like if you were to create a random string, then you will say something like the following.
This is equivalent to this one as well. So it is very important to understand how classes are
working in Python. So I will delete this line because this was just for an example. And now I
said that we are allowed to assign some attributes to instances of a class. So let's go ahead and
start creating attributes. And that will be achievable by using the dot sign right after the
instance of a class. And here you can say that you want to give it an attribute, like a name, that
will be equal to phone, and item one, that price could be equal to 100. And I think one dot
quantity could be equal to five, for example. Now in that stage, you might ask yourself, what
is the difference between the random variables that we have created to those four lines?
Well, here, we actually have a relationship between those four lines, because each one of the
attributes are assigned to one instance of the class. And I could probably do this by going ahead
and try to print the types of item one nil, and as well as the types of the attributes of name, price
and quantity. Now with name, price and quantity, we are not going to have any surprises because we
assign string type attributes to the item object. But if we were to print that, then check
out the result if I was to run this program, so you can see that now we have a data type of
item here. And that is the big difference between what we have seen previously to this thing that
we have just created. So now we understand how we can create our own data types. Now let's go
ahead and see what are the rest of the benefits using object oriented programming. Okay, so until
now, we understood how to assign attributes to instances, we should also understand now how we
can create some methods and execute them on our instances. Now, if we will take as an example, the
building class of string, then you know that we have some methods that we can go ahead and execute
for each of our strings. And for this example, you can see that I grabbed an instance of a string
that I named random str, and then I go ahead in the next line and execute the opera method, which
if you remember is it's possible to grab all the letters and turn them to uppercase. Now the
biggest question here is how we can go ahead and design some methods that are going to
be allowed to execute on our instances, Well, the answer is inside our class. So we could
go inside our class and write some methods that will be accessible from our instances. So we could
go ahead and say that and give our method a name. Now a good candidate for a metal that we'd like
to create now is actually calculate total price, because as we understand, it could have been
nice. If we were to have a method that will go ahead and calculate the result, multiplying it one
dot price, with item one dot quantity, so we can get the total price for that specific item. Now
before we go ahead and complete this function, then I'm going to just create one more instance
of this item by also deleting those two lines, because we understood the example. So I'm
just going to change those to item two, like that. And I'm going to use something like
laptop and change the price to 1000. And say that we have three of those. Now just a quick
side note, when you will hear me say methods, then I basically mean two functions that are
inside the classes. Because in terms of Python, or in any programming language, when you have
isolated definitions with this keyword, then those are considered to be called functions. But when
you go ahead and create those functions inside classes, then those are called methods. So that
is an important point that you should understand, because I'm going to call those methods
from now. Okay, so now if I was to continue by opening up and closing those parentheses,
then you are going to see one parameter that is autogenerated that Python wants us to receive
intentionally. Now the reason that this happens, Python passes the object itself as a first
argument, when you go ahead and call those methods. Now, if I was to go here, and
say item one dot calculate total price, then the action that we are doing now is calling
this method. But when you go ahead and call a method from an instance, then Python passes the
object itself as the first argument every time. So that is why we are not allowed to create
methods that will never receive parameters. Now you will see this if I was to remove the first
parameter, and say something like pass. Now if I was to execute this program now, then you're going
to see type zero, calculate total price takes zero positional arguments, but one was given.
So in simple words, what this exception says is that Python tries to pass one argument and you
are not received using any parameter, so that is very problematic. And that is why you always have
to receive at least one parameter when you go ahead and create your methods. Now since we always
receive this parameter, then it is just a common approach to call this self. It was okay if I was
to call it something like my perm, or I don't know something else. But you never want to mess up
with common conventions across different Python developers. So that is why just make sure that
you leave it as self every time. Now, if I was to go ahead and run this program, then you got to see
that we are not going to receive any errors. So this means that this method has been implemented
correctly. Now let's see how we are going to benefit from creating this method, because it
should go ahead and create a calculation for us using price and quantity. So I will intentionally
receive here two more parameters, which we could name just x&y for now. And we could just
say return x multiplied by y. And now I will go ahead and pass in here, two additional
arguments. And it will be item one dot price. The second one will be quantity. So that is
going to work because when you call this method in the background, Python passes this as an
argument. And then it passes the second argument. And then this has been passed as a third argument.
So that is perfect. And if I was to run that, and actually print this, so excuse me for running
this before printing it, so I will surround this expression with this print built in function.
And I will run that and you're gonna see 500 as expected, now I could do the exact same thing for
calculating the total price of our second item. So if I was to grab this and paste this in,
in this line, and actually change this to item two, and change this one to item two, and
as well as this one, then I will receive 3000 as expected. And that is how you can
create a metal. Alright, so until that point, we understood that we can assign attributes and as
well as creating some methods that we can go ahead and use them from our instances directly, like
those two examples in that line, and as well as in that line. Now in that episode, we are going to
solve some more problems that we have in terms of best practices in object oriented programming,
and things that you're going to see in each project that is based on Opie. Alright, so let's
get started. Now one of the first problems that we have here is the fact that we don't have a set
of rules for the attributes that you would like to pass in in order to instantiate an instance
successfully. And what that means, it means that for each item that I want to go ahead and
create, I need to hard code in the attribute name like those in here. And it could have been nicer
if we call somehow declaring the class that in order to instantiate an instance successfully,
name, price and quantity must be passed, otherwise, the instance could not have been
created successfully. So what that means, it means that it could have been a great option if we could
somehow execute something in the background. The second that we instantiate an instance and there
is a way that you can reach such a behavior. And that is by creating a special method with a very
unique name, which is called double underscore init double underscore. Now you might hear this
term as well as cold as constructor. Basically, that is a method with a unique name that you need
to call it the way it is intentionally in order to use its special futures. Now, the way that this is
going to work is by creating it the following way. So it will be double underscore. And as you can
see, I already have auto completion for some very special methods that are starting and ending with
double underscore. Now the collection of those methods are used to be called Magic methods. And
we are going to learn a lot of more magic methods that you have in Opie, but the first one that we
are going to start with will be the init double underscore, like that. Alright, so now that we
have created this method, then let's actually see what this metal does in the background. So when
you go ahead and create an instance of a class, then Python executes this double underscore
init function automatically. So what that means, it means that now that we have declared our
class, Python is going to run through this line. And then since an instance has been created, and
we have double underscore init method designed, then it is going to call the actions that
are inside this double underscore init double underscore method. Now in order to prove
that, then I'm going to start with a very basic point here that will say I am created Like that.
Now we got here, one instance. And here we got another one. So we should see I am created twice.
And in order to avoid confusions, then I'm going to delete those print lines from here so we can
see a cleaner picture. Alright, so if we were to run our program, then we can see that we have I am
created twice. And that is because Python called this double underscore init double underscore
method twice, thanks to those two instances that we have graded. Alright, so now that we use the
double underscore init function in this class, we should take benefit from it and solve some
more problems in order to implement Opie best practices. Now if you remember in the beginning
of this tutorial, I said that one of the problems that we have till this point is the fact that we
still hard code in the attributes in that way by saying dot name, dot price dot quantity. And
that is something that we can for sure avoid. Now let's see how we can start avoiding creating
those attributes hard coded for each of the instances here. So we can actually benefit from
the double underscore init method that we have designed. And let's see how now we understand
that for each instance that we will create, it will go ahead and call this double underscore init
method automatically. So what that means, it means that not only we can allow ourselves to receive
the self parameter, because this is a mandatory thing that we should do, because Python in the
background, passes the instance itself as the first argument, we could, in addition, take some
more parameters, and then do something with them. So as a solder, let's say that we would like to
receive one more parameter that we could name it name. And as you can see, automatically, Python
is going to complain how the name argument is not filled in here. So now, I could go ahead and
pass in the argument of phone for that one. And for the second one, I can go ahead and pass in the
argument of laptop. Now once I have created this, then I can actually go ahead and change my
print line a little bit. So it will be a unique print line where I can identify from where each
print line came from. So I can go ahead and say an instance created and use a column here and
then refer to the name like that. And now that we have created this, then if we were to run our
program, then you're gonna see unique sentences, an instance created for the phone, and as well as
for the laptop. Alright, so now that we have done this, then there is something that is still
not quite perfect, because we still pass in the attribute of name here and here. So now pay
attention to how the init method has to receive the self as a parameter as well. And we already
know the reasons for that. And the fact that we have self as a parameter here could actually allow
us to assign the attributes from the init method, so that we will not have to go ahead and assign
the attribute of name for each of the instances we create. So what that means, it means that
I can dynamically assign an attribute to an instance from this magic method, which is called
double underscore in it. So if I was to say, self, dot name, so I'm assigning the
attribute of name to each instance, that is going to be created or created yet,
and I'm making that to be equal to the name that is passed in from here. So what that means,
it means that now I can allow myself to delete this line. And then this line. So as you can
see, now I have a dynamic attribute assignment, thanks to the self dot name equals name that
we have wrote here in the to test that the attribute assignment world, then I can go down
here and use two more lines that will look like the following. So I will print it one dot name,
and I will also print item to that name. And in order to avoid confusions, then I'm going to
get rid of this line. So we could only see the print lines from here. And now if I
was to run that, then you can see that we receive a phone and laptop. So it means that we
were able to assign the attributes dynamically. And that is perfect. And now that we get the idea
of that, then we should also do the same for the rest of the attributes that we'd like to receive.
So we also got the price and quantity to take care of. So I'm going to go to my init
method, and I'm going to receive again, price and quantity. And I'm going to do the exact
same thing. So I'm going to assign the attribute of price. And that will be equal to price. And the
quantity will be equal to the quantity. And you can also see that again Python complains about the
price and the quantity not being passed in here. So I can say 100 and then five, and then I can
delete those. And then I can do the same here. I could pass In 1000, and then three, and delete
those, and in order to prove that this is going to work, then I'm going to copy myself a couple of
times and change this to quantity, I mean price, this one will be price as well. This one will be
quantity and this one as well. Now if I was to run that, then you can see that the results are
as expected. So that is a way that you should work with the double underscore init method,
you should always take care of the attributes that you want to assign to an object inside the
double underscore init method meaning inside the constructor. Now a couple of signs that are quite
important to remember when we work with classes. Now when we go ahead and use the Ws coordinate
method, this doesn't mean that we cannot differentiate between mandatory parameters to non
mandatory parameters. So say that you currently don't know how much you have from a specific
item, then you can go ahead and by default, received this quantity parameter as zero, because
it is realistic situation that you currently don't know how much phones you have on your store.
so we can directly go ahead and use a default value for that, for example, zero, and then this
will mean that you will not have to pass in those five and three here. And now in order to show you
the results of that, if I was to run our program, then you can see that we receive zero twice for
those two prints in here. So that is something that you will want to remember. And one more quite
important point that I'd like to talk about now is the fact that you can assign attributes to
specific instances individually. So say that you want to know if the laptop has numpad are not
because some laptops are not having the numpad on the right side of the keyboard. But this is not a
realistic attribute that you will want to assign to a phone. And that is why you can go ahead
and let me delete those print lines, by the way. And that is why you can go ahead and say something
like item two that has numpad equals to false like that. And that is something that you want to
remember, because the fact that you use some attribute assignments in the constructor doesn't
mean that you cannot add some more attributes that you will like after you instantiate the instances
that you would like to. Alright, so now that we understood this, then there is still one small
problem that is left that we need to solve. Now pay attention how the calculate total
price still receives the x and y as parameters. And the question that we asked now is why it still
receives those parameters. Well, we could for sure now not received those parameters. Because as we
know, for each metal that we design in classes, then the object itself is passed in argument. And
I know that I repeated this a couple of times. But this is where I failed to understand classes.
So that is why it is very important to understand this behavior. And we already know that the
object itself passed as an argument. So that's why we receive self. And so this means that now
we could just return self dot price multiplied by self dot quantity. And this will mean that we
don't really have to receive those parameters, because we assign those attributes, once the
instances has been graded. So this means that we have access to those attributes through how
the methods that we are going to add here in this class in the future. So in order to
test that this works, then I'm going to delete this example for now. And I'm going to say
print item one dot, calculate total price. So we will be able to return the result here. And I will
do the same for item two, sorry, only this one. Now to show some real number other than zero, then
I will go ahead and pass in here, quantities. So I will say one and three, for example, because I
don't want to multiply a large number which is zero. And that could come from here. So I will run
that. And you'll see that we receive the expected results. So now we completely understand the big
picture, how to work with the constructors in classes, and what are the best practices that
you should go ahead and implement. Alright, so now that we understood this, then we might
think that we have done everything perfectly. But actually I want to show you what will happen
if we were to pass in here a string besides an integer and run our program. So if we were to
run that, then you can see that we are screwing things up here. Because this function for example,
things that he chose to print the string three times because you'll see we have 1000 multiply
by three that is being returned in here. So it shows us 1000 once, 1000 twice, and then one more
time. So what that means, it means that we have to validate the datatypes of the values that we
are passing in. So there are a couple of ways to achieve this. And one way is by using typing's in
the parameters that you're declaring inside here, so a great starter will be, for example, to
declare that a name must be a string. Now, let me first take this back and change those to integer
and then go here and design those parameters. So in order to specify a typing, then you should
go ahead and create a colon sign, followed by the type of the datatype that you expect to receive
here. So if I was to pass in here, only the object reference to the class of str, then it will mean
that it will have to accept strings only. And I can prove that by changing this to an integer. And
you're going to see that we have a complaint here that says expected type str God int instead. And
that is perfect. So now that we have done this, then I'm going to do the same for the price
itself. And the price, we could actually do the same thing with it by passing in float. Now when
we pass float, it is okay to also pass integers. And that is something very unique with floats
and integers together. So that is okay to use the typing of float. And for the quantity, we
don't need to specify a typing, because the fact that we passed a default value of integer already
marked these parameter as to be integer always. So that is why, for example, if I was to leave this
as it is and change the quantity to a string, then you're gonna see that it is going to complain,
because the default value is already an integer. So it expects for an integer. All right, so those
things are actually great setups to make our init function more powerful. But we might still want to
validate the received values in the following way. So say that you never want to receive a negative
number of quantity. And you'll never want to receive a negative number of price. So that
is something that you cannot achieve by the typing's in here. But there is actually a great
way to work this around. And that will be by using assert statements. Now assert is a statement
keyword that is used to check if there is a match between what is happening to your expectations. So
let's see how we can get work with assert. So I'm actually going to delete this from here. And I'm
going to organize our init method a little bit, I'm going to say here a comment and I will say
assign to self object. And I will say up top something like run validations to the received
arguments. Alright, so now it is a great idea to validate that the price and quantity are both
greater than or equal to zero, because we probably don't want to handle with those when they are
negative numbers and we want to crash the problem. So we could say assert and pay attention that
I use it as a statement not a built in function or something like that. And I can say here,
price is greater than or equal to zero. Now once I said this, then I can also do the same for
quantity, actually. So let me do that quickly. By this way, and then once we have this, then
I can actually go ahead and run our program. And you will see that I will not receive any
arrows. But the second that I change this quantity to negative one, for example, and this one being
negative three, then I will have some arrows that will say, assertion error. Now you can see
that the fact that we see here, assertion error is quite a general exception, that doesn't
mean anything. Now what is so beautiful with a third, you can add your own exception messages
right near of it as a second argument. So let's go up top here and go back to those two lines. So the
first argument that is passed to the statement is the statement that we'd like to check. But if we
were to say here comma, and use a string to say, actually formatted string, and I can say price and
then refer to the value of it is not greater than zero like that. They can add an explanation
mark here, and they can use the same thing. Copy that with a comma and paste this
in here. And changed this quantity and then refer to the value of it and say that
it is not equal to i mean greater than or equal to zero. So we need to be actually changed
to greater than or equal to, like that. And same goes for here, and I have some a
space here that will be deleted. All right, so now if I was to execute our program, then you
can see that we receive assertion error quantity minus one is not greater or equal than zero.
So I should delete this, then here for that, and now it is perfect. So now we understand
that using the assert statement could allow us to validate the arguments that we receive.
And also, it allows us to catch up the bugs as soon as possible, before going forward with
the rest of the actions that we'd like to take within this program. So let me actually change
those back to valid values like that. And that is perfect. Alright, so until this point, we
learned about how to work with the constructor. And we also learned about how to assign different
attributes to instances that are going to be unique per instance, which means that you can go
ahead and create as much as instances as you want, and you have the control to pass whatever
values you would like to for the name, price and quantity. Now consider a situation that
you'll want to make use of an attribute that is going to be global, or across all the instances
now are a good candidate, for example of this could be a situation that you will want to apply a
sale on your shop. So this means that you want to go ahead and having the control of applying some
discount for each one of the items. And that is a good candidate for creating an attribute that
is going to be shared across all the instances. Now we call those kinds of attributes, class
attributes, and the kinds of attributes that we have learned that till this point is actually
called in a full name instance attributes. So about instance attributes, we know
everything, and we learned how to work with it, but we did not work it with the other kind of the
attributes, which we will do in this tutorial, which is called again, a class attribute.
So a class attribute is an attribute that is going to be belong to the class itself. But
however, you can also access this attribute from the instance level as well. Let's go ahead
and see a good candidate for a class attribute that you want to go ahead and create it. So
that's going to be going to our class here. And just in the first line inside our class,
I can go ahead and create a class attribute. So let's go ahead and create an attribute like
pay rate equals to 0.8. And the reason that I'm doing this is because I said that there is going
to be 20% of discount. So I probably want to store an attribute that will describe how much I still
need to pay. So I will say here, the pay grade after 20% discount like that. Okay, so now that
we have created this, then let's see what are the ways that we can access this attribute. Now, if I
was to go down and actually deleting one of those, and say something inside this print line that will
look like the following. So I will try to access to the reference of the class itself. So I'm not
going to create an instance like that, besides, I'm just going to bring in the reference to the
class level itself. And I'm going to try to access this attribute by saying the PE underscore rate.
Now if I was to run that, then you're going to see that as expected, we see this class attribute,
because that is a way that you can access those class attributes. Now this might be
confusing, but I said a minute ago that you can also access those class attributes from the
instance level. Well, let's see if that is true. So if I was to duplicate those lines twice, by
using the shortcut of Ctrl D, then let's go ahead and change those to item one, and this one to
item two. Now see how I try to access the pay rate attribute from the instance, although we don't
have such an instance attribute. Now if I was to run that, then you're going to see that we still
have the access to see that class attribute. Well, that might be confusing. And that might be
hard to understand why that is happening. Well, there is actually something that we need to
understand when we work with instances in Python. So when we have an instance on our hand, then At
first this instance tries to bring the attribute from the instance level at first stage, but if it
doesn't find it there, then it is going to try to bring that attribute from the class level. So what
that means it means that item one did something in here and say to itself, okay, so I don't have this
attribute right in here because that is just not an attribute that assigned to me. So I'm going
to try to search that from the instance level and then I'm going to find it and if sprinted
back. So that is exactly what is happening here. Item one and item two are instances that could not
find the pay rate attribute on the instance level. So both of them went ahead and try to bring
this attribute from the class level. And since it really exists in the class level, then we
were able to access those. Now to even give you a better idea of what is going on here. Then
I'm going to do one more additional thing. Now I will delete these first print line. And I will
go ahead and delete those attributes from here as well. Now there is a built in magic attribute,
not a magic method, that you can go ahead and see all the attributes that are belonging to that
specific object. And that is achievable by using this double underscore vi CT double underscore
like that. So this will go ahead and try to bring you all the attributes that are belonging
to the object that you apply this attribute and want to see its content. So I will go ahead and
copy this one and paste this in for the instance level as well. So this will give me all the
attributes for class level. And the second line will do this for the instance level.
Alright, and if I was to run that, then let's explore the results for a second. Now
we can see that at the first line, we see this pay rate attribute. But in the second line, we
never see it, we see name, price and quantity. And you can also pay attention that this magic
attribute is actually responsible to take all the attributes and convert this to a dictionary. And
that is from where the dict keyword coming from it is just a shortened version of a dictionary.
So that is a very useful magic attribute that you can go ahead and use if you just want
to see temporarily for debugging reasons, all the attributes that are belonging to some
object. Alright, so now that we understood this, then let's take it to a real life example and
come up with a method that will go ahead and apply a discount on our items price. So that will be by
creating a method that will we belong to each of our instances in that means that we can go ahead
and come up with a method that we could name apply discount. So let's go ahead and start working
on this. So I'm going to say def apply, discount and pay attention that I'm using a new method
inside a class here. So right inside of this, then add first we need to figure out how we are
going to override an attribute that is belonging to an instance. And we already know that we can
do that with the self keyword. So it will be self dot price. And that will be equal to self dot
price, meaning the older value of this attribute multiplied by the pay rate. Now you might expect
that we could access this directly like that. But if you remember, that is actually belonging to the
item class itself. Now this might be confusing, because this method already inside this class.
So you might think already that you can access it directly by saying pay rate, because it is
already inside the class. But that is actually not going to work. Because you can either access it
from the class level or the instance level as we understood previously. So we can go ahead and say
item dot pay rate like that. And there you have a metal that can go ahead and basically override
the price attribute for one of your items. Now to show you that this works, then I can only
use one instance for now. And I can go ahead and call this method by saying apply discount. And I
can also now try to print the attribute of price for this item one, and we should see ad right. So
if we were to run that, then you're going to see that we are going to receive at point zero as
expected. Now we should not forget the option that you might also want to have a different
discount amount for a specific item. So say that one day you will have 20 items or in only for the
laptop, you will want to have a 30% discount. But it is going to be a bad idea changing the class
attribute to 0.7 because it will affect all the items that you have right now on your hand. So
what you can do instead is you can assign this attribute directly to one of the instances that
you would like to have a different discount amount for so let's go ahead and see an example for
this. So I will allow myself to bring back the item or laptop and then what I can do to apply
a 30% discount for this item is assigning the exact same attribute to the instance. So I can go
ahead and use a item to that pay on the score rate is equal to 0.7. Now what will happen here is
that for item two, it will find the attribute of pay rate in the instance level. So it does
not really have to go ahead to the class level and bring back the value of pay rate because
Add first look, it is going to find it in the instance level. But for item one, it is different,
it is still going to read from the item level, which is going to be 0.8. So now, if we were
to try to use item two dot apply discount, and as well as printing the price now, then let's
see what will happen. So I will uncomment this line to not see this screen for now. And I will
go ahead and execute our program. Now you can see that we still, however, receive 800. And what this
means this means that the discount that has been applied is still a 20%. And where this is coming
from, well, this is coming from this method here that no matter what we try to pull the pay rate
from the class level. So a best practice here will be to change these two cells. And that way, if
we override the pay rate for the instance level, then it is going to read from the instance level.
But for item one, if we try to access the pay rate from the instance level, then this is still great,
because we did not assign a specific pay rate for item one. So it is going to pull that from the
class level. Now if we were to try to run that, then you're gonna see now that we have expected
results. And if we were to also uncomment, the first print line for the item one and rerun
our program, then you can see that for item one, we had 20% discount. And for item two, we had
30% discount. So when it comes to accessing class attributes, you might want to reconsider
how you want to access them when you will come up with some methods. And specifically
for creating a method like apply discount, it is a great idea to access it from the instance
level. So you also allow the option of using a pay rate that is assigned to the instance level.
Okay, so now that we understood completely about the differences between a class to an
instance attribute, let's jump ahead to the next topic. Now you'll see that I have deleted
those print lines that I have down below. And I came up with five instances that I have
created here. So you might also want to create those five instances immediately. So that is why
I will recommend you to go here to my repository, accessing these class attributes directory, and
then code snippets, and then go ahead and copy the code from these five underscore items.py file.
Okay, so considering a situation that your shop is going to be larger in the future, meaning that you
are going to have more items, then the more items that you're going to have the more filtration
like things that you want to do in the future. But what is problematic currently with our class
is the fact that we don't have any resource where we can just access all the items that we have in
our shop right now. Now, it could have been nicer if we could somehow have a list with all the item
instances that have been created until this point. But currently, there is not an approach that
will give us a list with five elements where each element will represent an instance of a class.
So in order to come up with such a design, then here is a wonderful candidate for creating a class
attribute that we could name all. And once we do this, then we're going to see how we are going to
add our instances to that list. So I will go ahead and start by going here and use in all attributes.
So it will be all equals to an empty list. Now we need to figure out how we are going to add
our instances for each time that we are going to go ahead and create an instance. Now if you
remember, the double underscore init method is being called immediately once the instance
has been graded. So it might be a wonderful idea going down below inside this double underscore
init method and use a code that will be responsible to append to that list every time
that we create an instance and that will be as easy as saying something like the following. So
first, you could pay attention that I actually wrote some commands in this double underscore
init function like run validations and assigned to save object. So it might be a great idea to
start with a comment here that will say actions to execute just to really have a great separation
between the different things that we are doing. So now inside here I can say item dot all and
you can see that I use the class object first and then that is a list so I can use dot append
and then I will just append the self object. Now we know that self is actually the instance itself
every time that it is being created. So once we go ahead and launch such a command inside the
unit Then for each instance, that is going to be created, this all list is going to be filled with
our instances. Now to show you that I can jump line after we create the instances, and we can say
print item that all. And now if I was to run our program, then you're going to see that we're going
to have a list with five instances. If I was to scroll right a bit, then you can see that I have
exactly five elements. And that is perfect. Now that's going to be extremely useful if you want
to do something with only one of the attributes of your instances. So say that you'd like to
print all the names for all of your instances, then you can use easily a for loop to achieve such
a task. So we can go ahead and say, for instance, in item dot all and you can say print instance,
dot name. And once we come up with this, then you can see that we have all the names for all the
instances that we have graded. So that is going to be useful here and there, especially if you know
how to use the filter function, for example, to apply some special things on some of the instances
that are matching your criteria. Alright, so now that we understood this, then let's also take care
of one problem that we saw previously. Now if I was to use a Ctrl, D couple of times, and still
use this print item dot all now you could see that the way that the object is being represented
is not too friendly. Now, it could have been nicer if we could somehow change the way that the
object is being represented in this list here. Now, there is actually a way to achieve this by
using a magic method inside our class. Now there is a magic method that is called double underscore
our EPR. And our EPR stands for representing your objects. So that is why you can actually go
ahead and use this magic method. And then you will have the control to display your objects
when you are printing them in the console. Now, I actually recommend watching a video that compares
between a metal that is similar to it, which is called double underscore str. And you can take
a look in the description of this entire series to actually watch the video that I'm talking
about. Alright, so let's go ahead and use the RPM method to understand how this is going to work.
So I'm going to say def inside our class. And I'm going to use double underscore r e, PR double
underscore and as expected, it will receive the self. Now what we can do now is returning a string
that will be responsible to represent this object. Now obviously, we don't want to use something that
is not unique for each of the instances. Because say that I was to use now return items, something
like that, and run our program, then you can see that I'm going to receive a list with this string
five times. But it is going to be hard to identify which instance represents each string here. So
it could be helpful if we were to return a string that could be unique. So I'm going to close the
console here and go ahead here and use a formatted string. And in order to make this unique, it is
a best practice to represent it exactly like we create the instance like that. So what I'm
going to do here is living the item and use a brackets opener and the closure. And then I'm
going to make the return string here as much as equal as possible to the way that we create
those instances. So I will start by typing here single quotes to escape from the double quotes
that are coming from here. And I'm going to refer to the value of name by using self dot name. And
then I will leave my single quotes. And I will use a comma like that. And then I will go ahead
and refer to the value of our price. I will use one more comma, and I will say self dot quantity.
Now if we were to execute our program again, then you can see that now we receive a list that is way
more friendly than what we have seen previously. And you can also see that this first element, for
example, is quite equivalent to this line here. Now you might be curious why I worked so hard to
return the representative version of our objects the same way that we create them. So that is
just the best practice according to pythons documentations because it will help us to create
instances immediately by only the effort of copying and pasting these To the Python console.
So if you think about it right now, if you open a Python console, and you'll import this class, then
it will be as easy as grabbing this and pasting to the Python console. And then you will have an
instance being graded. So that is the single reason that I have came up with this approach. And
also for sure, I just wanted to return a unique string that will really represent our instance.
And you can see that it is very easy to identify the instances of our class with this list. And
with this approach, alright, so until this point, we understood how we can change the way that we
represent our objects. And we also understood how we can access to all of our instances by this
class attribute that we intentionally named all. Now in this part, we are going to take a look to
solve one more problem that we have in terms of best practices when we are going to extend this
application and add more features. Now you can see that until this point, we maintain our data as
code in this main.py file by only instantiating. Those five items. Now when we will look to extend
this application and add some more futures, then we might have a harder life to add those
features because the actual data and the code are maintained in the same location, meaning
in the same main.py file. Now you could think about creating a database that will maintain this
information. But I want to keep things more simple for the purposes of this tutorial. And that is
why I'm going to use something that is called CSV that you might have heard of. csv stands for comma
separated values. So this means that you could go ahead and use a CSV file, and you could store your
values as comma separated where each line will represent a single structured data in CSV is
a great option here because it allows the data to be saved in a table structured format.
Alright, so let's go ahead and create a CSV file. And I will actually go ahead and name
these items, dot c is V, like that, and I will go ahead and paste in some CSV content that will
be responsible at the end of the day represent the same data that we look to have here. So you can
see that at the first line, I have name, price and quantity. And you can see that those are comma
separated. So those represents the columns that we're going to have as the data that we are going
to maintain. And in the second line and further, we are going to have some data that will represent
the actual data that we look to maintain. So if we were to now split the panes, then you can see that
those are quite equivalent. And now we should only look for a way to read the CSV file and actually
instantiate those objects. Now we can see that I have a suggestion by pi charm to install a plugin
that will support CSV files. So I'm going to just click on that and install those plugins. And you
can see that I will have a CSV reader here. And we will see if we will be able to see this data
in a table, which will be a lot nicer. So let's go ahead and install this. And now you can see
that I have some more options that I can actually go ahead and use from here, I know that this is
quite small, but actually you have some tabs that you can go ahead and click on them. And if I was
to click on table editor, and actually give this file more focus, then you can see that I actually
have the best way to read this data. Now, you can see that I have my columns, you can see
that I have my rows. And that is quite nice. Now I can really go ahead and visualize my data more
nicer. And it's just more common way to maintain your data. Okay, so now that we understood how CSV
files are working, let's go ahead and read our CSV files and instantiate the instances in a generic
way. So it makes sense to delete those five lines. And I'm going to use those lines below
the apply discount and use a metal that I could name instantiate from CSV like that. Now,
you can see that this one is also going to receive itself because if you remember I said
that in each metal that we will design, we need to receive at least one parameter that
will be passed as the instance itself, because this is how Python op works. Now, the problem is,
we are not going to have any instances on our hand to call this method from the instance because
this method is actually designed for instantiating the object itself. So this means that this
method could not be called from an instance. So the way that this is going to be solved is by
converting this method into a class method. Now, a class method is a method that could be accessed in
the following way. So I will use this line tool, delete that, and it could be accessed from the
class level only. So this will look like item dot instantiate from CSV, and then in here, we will
probably pass our CSV file. So this method should take full responsibility to instantiate those
objects for us. So now that we understood this, let's go ahead and see how we can create a class
method. So for sure, we need to delete the self. And I know that we have arrows, but we are going
to solve each one of those just in a second. Now in order to convert this to a class method, we
need to use a decorator that will be responsible to convert this method into a class method. Now
decorators in Python is just a quick way to change the behavior of the functions that we will write
by basically calling them just before the line that we create our function. So we could use the
Add sign and use the class method in here and then this instantiate from CSV method will be a class
method. Alright, so now that we understood this, then we should also understand one more piece of
information before we go ahead and design this method. Now I want to show you what will happen
if I was to delete the entire name and try to recreate this function here. And I will just say
instantiate from CSV again, Now pay attention what will happen if I was to open up and close the
parentheses, now we can see that it still receives a parameter, but this time, it is named CLS. Now,
what is going on here, and the thing that is going on here is the fact that when we call our class
methods, then the class object itself is passed as the first argument always in the background.
So it is a bit alike the instance where it is also passed as the first argument. But this time,
when we call a class method in this approach, then the class reference must be passed as a
first argument. So that is why you should still receive at least one parameter, but we probably
understand that we could not name this self, because that is just going to be too much
confusing. Okay, so now let's go ahead and write some code to read the CSV file and instantiate
some objects. Now, I'm going to go up top first, and I'm going to import a library that is called
CSV. So I will go here and I will use an import CSV line, because that will be the library that
will take full responsibility to read the CSV file. And then we will see how we can instantiate
some objects. All right, so now I can go ahead and use a context manager to read the items dot CSV
file. Now both of those files are located in the same location. So I can just directly say, Wait,
open items dot CSV, and the permission that I will be passing here could be hour because we only
look to read this. And I will say as f like that. Now inside this open, I will go ahead and use some
metadata to directly read the CSV, which at the end of the day will be responsible to convert this
into a Python dictionary. So I will say reader is equal to CSV, dot d ICT reader like that. And
I will pass in the content of our file like that. Now, this method should go ahead and read our
content as a list of dictionaries. But at the end of the day, we should also go ahead and convert
this into a list. So I will go ahead and create one more variable that will be equal to items.
And I will just convert the reader into a list. And that's it. And now that we have completed
the actions that we want to complete by reading the CSV file, let's go ahead and use a Shift
Tab to indent out. And now before we go ahead and instantiate some objects, let's go ahead and
see the results of iterating over the items list. Now I will go ahead and use for item in items.
And then I will just use print items to show you the behavior of that. And excuse me, it should be
item. All right, so now that we understood this, then let's go ahead and see what we have in those
lines. So after our class definition, we only go ahead and call this item dot instantiate
from CSV method. So if I was to run that, then you can see that I received some dictionaries
in separate lines. And that is because I iterate over a list of dictionaries in Here, and that is
just perfect. All right, so the only thing that we miss right now is creating instances. Now besides
printing those, then we could now say something like item and open up and close parentheses. And
this will be enough to instantiate our instances. Now I can go ahead and pass my arguments in here
by basically reading the keys from a dictionary. So I can say name is equal to item dot get, and
that will receive name. And now I can add a comma and duplicate this line twice, and change
those accordingly. So this will be price. And this will be quantity. And now I need to
replace my key names. So it will be price here, and then quantity right there. And now let's go
ahead and see what will happen if I was to call this method. And as well as calling the attribute
of item dot all because this one stores all the instances inside the list. Now if I was to go
ahead and run it, then you can see that I have some arrows. Now you'll see that the arrows are
related to the price. And you can see that we receive is not greater than or equal to zero.
Now let's go ahead and fix this very quickly. So in the items dot CSV, you can see that those
are actually integers that are greater than zero. So the problem is probably the fact
that those are passed as strings. So we need to go ahead and pass those as integers. So
I'm going to convert those into int, like that. And now let's go ahead and see if we will have any
problems as I expect to have any problem, because the quantity should complain about the same thing.
And you can see that this is exactly what is going on here. So we can use the same for quantity like
that, and work with that. And you can see that now we see our instances perfectly. Now I want to show
one more problem that we could have in the future and we should avoid now. So those three lines
are going to work with this structural of Aveda. But if I was to change the price of our keyboard
to something like 74 dot 90, something like that, and re execute our file, then you can see that we
will receive some problems. So we need to convert the price not to an integer but to a float like
that. And that is the only way to get over this because we don't want to convert the price
to an industrial directly because it could be float. So now we could go ahead and execute
and you can see that now it works perfectly, although we see the prices as 100.0 but that is
something that we will look into it in the future but for now it works perfect. And now we are ready
to jump on to our next topic. Okay, so now that we completely understood the class methods, let's go
ahead and also understand what static methods are now established metal show do some work for you,
that has some logical connection to a class. So for example, if you want to check if a number
is an integer or a float, then this is a good candidate for creating a static method, because
this has some connection to the class that we work with. So it makes sense to check if a price
of an item has a decimal point and by saying has a decimal point, I obviously count out those that
are point zero. Now to be honest, static in class methods could look very alike to you. But we
will explain the main differences very soon. Okay, so I will use those lines to create our
first static method. Now let's go ahead and use the def keyword. And we will name this method
is underscore integer because we said that we'd like to write a static method that will check
if a received number is an integer or not. Now if I was to open up and close parentheses, this
would obviously receive itself now I want you to take a closer look what will happen if I was
to change this method into being a static method and the approach is going to be pretty much the
same like we have done with the class method, we will use a decorator that is called static method
and this will be responsible to the conversion. So I will go ahead and use this line and I will say
add static method like that. Now pay attention how the received parameter turned into the
regular orange color that we are familiar because that is just a regular parameter that we
receive. Now this means that this static methods are never sending in the background, the
instance as As a first argument and that is unlike the class methods, the class methods are
sending the class reference as a first argument. And that is why we had to receive the CLS. And
that is why it is intentionally colored with purple. But with static methods, we never send
the object as the first argument. So that is why we should relate to the static method, like a
regular function that just receives parameters like we are familiar with isolated functions. Now
I will go deeper on this just in a few minutes. But let's go ahead and finish up our static
method first. So this should receive num as a one parameter because we should receive at least
something to check if it is an integer or not. All right, so now that we are inside this method,
then I can go ahead and use a couple of statements to check if the received argument is an integer
or mark. Now if you remember, we said that we'd like to, we will count out the floats that are
decimal that are point zero, okay? Meaning, for example, 5.0 10.0, and so on. Alright, so
now that we understood this, let's go ahead and use an if statement view. So if in we will
call the built in function that is called is instance. And this should receive two arguments.
And we can understand what this function is going to do for us, it is going to check if the received
parameter is an instance of a float or an integer. So we will pass in as the first argument the num
and as the second argument, the float without calling those parentheses, so only the reference
to the float keyword. So this conditional should go ahead and check if the num is a folding
number or not. Now inside this if statement, I will say return num.is integer, so by
saying.is integer, then I basically say count out the floats that are decimal that
are point zero. So this means that if I was to pass in here a number like 10.0, then this will
return false, but remember that this will enter here because he thinks it is a flaw because it is
represented in that way. And so the East amisco interview should check if the point is zero, and
true return false accordingly. Now, I will also use an else if statement here to basically check
if it is integer by itself. So I will say l E is instance num, and check if it is an instance of
an integer, then I will just return true. And if it is just something else, then I will just
say return false like that. So now that we have designed this method, then let's take a look how
we can call it. So now I will just remove this and this, I'm not actually going to instantiate
anything, I'm just going to show you how you can access to the static method. So I will just call
this item.is interview and I will just pass in a number that I will like to check if it is an
interview or not. Now for sure, we'd like to print this. So we will see the result. Now let's go
ahead and pass in seven. So, you can see that we receive through now if I was to pass in 7.5 then
I would receive false and what is happening in the background it is the fact that it enters here, but
it sees that it is not an integer so it returns false. But if I was to change this to 7.0
then this show Sorry about that, this should still return true because what is happening
it is entering inside this conditional and then it checks if it is an integer, but we said
that this method counts out the floats that are point zero. So it returns true still so that is a
perfect design Alright. So I have came up with a new file, which I will just explain here when
to use a class method and when to use a static method. So we can completely understand the
differences between those because I remember myself I had a very tough time to understand why
I need this and why I need the other one. So that will be the main question that I will be answering
in this Python file. So don't feel like you have to copy and paste the code following along what I
am explaining here by listening should be enough Alright. So in this file, I will just go
ahead and create this class item that we have right and i will use pass to not receive
arrows. Now when we will use a static method. So we will use a static method when we want to do
something that should not be unique per instance. Exactly like we have I have done previously. So
his interview is a method that is just going to be responsible to check if a number is integer
or not. So that is why I could allow myself to include this under the item, just like I could use
this def as an isolated function right above the class. And that was also okay. But I prefer
to not do that, because although this is a metal that has nothing to do with instance,
that is somehow related to the item class. So that is the reason you want to create this as
a static method, like we have designed previously. And the reason that you would like to create
a class method is for instantiating instances from some structured data that you own. So
exactly like we have done, we have created a class metal that was responsible to read the
CSV file and creating some instances. So as I wrote here, those are used to manipulate different
structures of data to instantiate objects, like we have done with the CSV file, we could also use a
class method like instantiate from a JSON file, or from a yamo file, those just are different ways
to maintain data in the best practice way in that is the code that you will look to include inside
your class methods. That is why they should be existing in any class, especially if you
look to instantiate hundreds of objects on your programs. So it is a great idea to have at
least one class method, like we have done in the item class. Now the only main difference between
a class method and to a static method is the fact that static methods are not passing the object
reference as the first argument in the background, it is noticeable from the fact that we don't
have a special highlight purple, in my case for the first parameter. So if you remember, if I
was to go ahead and use here a first fundamental like num, then you will see that this is the
first parameter that is colored with orange, because that is a regular parameter. But that is
purple, because this is a mandatory parameter that we should receive, because what I have just
explained, so those are the main differences between a static method to a class metal. Now
if you remember, I intentionally said that the class methods and the static methods could only
be called from the class level. But however, those also could be called from instances. So as
you can see, I can actually instantiate an object and call the integer in as well as the instantiate
from something can just pass in here in number like five and I will not receive any arrows.
And if I was to run the helper, then you can see that I don't have an error. Now, I'm going
to be honest with you, I never saw a reason to call a static method, or a class method from the
instance level. But that is just an option that exists, I know that it is very, very confusing.
But that is something you are rarely going to see. And like I said, I never saw a great reason
to call a static method or to call a class method from an instance. So my recommendation
to not confuse you is just not going with calling those from the instance level. All right,
so I minimize the code that we wrote so far in the class item. Now in order to start solving
the problems that we will solve in this episode, then I'm going to create here two instances.
So I will say form one is equal to an item. And let's give it a name like JC phone v 10.
And then just use a random price and quantity. And I will copy and paste this and use another
variable like phone two in there, we'll increase the version by 10. And let's say that this price
for the phone two should be 700. All right, so now that we have created two instances of a phone,
pay attention that those two items are phones. So we could think about some attributes that could
represent phones in real life. Think about an attribute like broken phones, because we could
have some phones that could have been broken. And so we cannot really mark it as a phone that we
could really sell. So this means that we could go ahead and say phone one that broken phones, let's
say that we have unfortunately one broken phone on our hand right now. So I will go ahead and
assign the same attribute for our second phone. And now that we have came up with this realistic
attribute, then the next step that we might think about, could be creating a method that will go
ahead and calculate the phones that are actually not broken, meaning subtracting the quantity by
the broken phones amount because this is totally making sense. And then we can understand what are
the phones that we could go actually and sell them in the future. But we have couple of problems
creating a method that will go ahead and calculate Such a thing because we cannot we'll go ahead
inside our item. And do this smooth enough, because we don't really have the broken phones
attribute assigned to self. And we cannot actually go ahead and create this method inside this
item class, because this method is not going to be useful for other hundreds of items that you
will go ahead and create. These just represent a phone kind of item. So in order to solve this
problem in terms of best practices in object oriented programming, then we could go ahead and
create a separate class that will inherit the functionalities that the item class brings with
it. And that is exactly where we could benefit from inheritance. And we could go ahead and create
a separate class that we call name phone. And then this phone class will inherit all the methods in
all the attributes that item class has. So let's go ahead and simulate that. So I'm not going
to delete the instances yet, but I'm going to go ahead here and create a class that I will name
it phone. Now pay attention that I will not use a semicolon and I will use those brackets and I will
specify what class I would like to inherit from. So I will inherit from item. And then I will just
use a pass temporarily because I will not like to use additional functionality right now inside
this class. Okay, so now that we have created this class, then let's go ahead and first execute our
program, where at the first stage, the instances will be item instances. And this should not have
any problems because we know that we can create those item instances and we will not receive
any arrows. But if we were to change those to phone like that, then we should still
not receive any arrows. And that's just a basic way that you could use inheritance in
order to represent different kinds of objects when you want to do that. Now, this could also
be applied to other real estate programs that you want to come up with them and buy your own. But
in my case, it totally makes sense to create some classes where each class will represent a kind
of an item. And then I could go ahead and inherit from the item class in each of the child classes
that I will go ahead and create in the future, I could also use another class for a kind of item
like laptop, and then I could go ahead and use the separated functionality for that. Now when we talk
about classes that we inherit from, then those are considered to be called parent classes. And when
we use multiple classes that inherits from that parent class, then those are considered to be
called child classes. So those are just terms that you want to be familiar with when we talk
about object oriented programming. And from here, we will see more advanced things that you can go
ahead and do with your child classes. Alright, so now let's go ahead and understand some more
advanced things about inheritance. Now to help this series, we learned that it is not a great
idea to assign attributes manually, once we create those instances. And the better way to do that is
actually going ahead and creating our constructor and pass the value that we'd like to immediately
in the instance creation exactly like here. So in order to solve this, then we're going to
need to figure out how we are going to do that, because creating the constructor inside
this phone class is going to will be tricky, because we don't really want to break the logic
that the development score in it brings with the parent class. But we'd also like to pass in an
additional attribute like broken phones, that we will go ahead and deal with that attribute and
assign it to the self object exactly like we have done in the second part of our series. So in order
to keep the logic the same for this child class, and as well as received some more attributes. Then
for now, I am going to go ahead and copy the code in our constructor and just paste this in right
inside our phone class. And that's making sense temporarily, because we received the exact
same parameters that we should receive when we instantiate an instance. And we also have
now the control to receive some more parameters, like we want to do with the broken phones.
So let's go ahead here and say broken. So I will just scroll here, and I will say broken
phones is equal to zero. Let's also receive a default value for that. And let's go ahead and
type in a validation for the broken phones. So I will allow myself to just copy that and paste
this in. And we'll use assert quantity I mean broken phones is greater than or equal to zero
and I will change this to broken phone Like that, actually broken phones, and this should be
exactly like we have done with the quantity. And now let's go ahead to the section of assigned
to self object. And we can use self dot broken phones is equal to broken phones like that.
And you can see that here we have actions to execute. Now it could have been nicer if we
could also create a class attribute for the phone class. And that will mean that we could go
ahead here and say all is equal to an empty list, then we could go ahead and use a form dot all
dot append, like that. And now if I was to go ahead and run this program, then you can see that
I will not receive any arrows. Now to check that this works, then I'm also going to pass in
here one. And I'm going to do the same here as well. And I'm going to remove those. All right,
I'm going to remove the hardcoded attributes, and the program still works. Now I'd also like
to test this by applying one of the methods that we have wrote so far in that will be obviously a
method that I like to use from the parent class, because we inherit those methods. So
I can go ahead and use phone one dot, calculate total price, and it makes sense to
print this. So I will go ahead and print that. And you can see that print phone one dot calculate
total price. And now if I was to run that, then you can see that I received a result.
So this means that I don't have any arrows. Now I'm not sure if you pay attention to
this. But if I was to scroll up a bit, then you're gonna see that the constructor in the
child class is complaining about something. And let's Hover the mouse and see what is the warning.
Now you can see that it says to us call to double underscore in it of super class is missed. And
what that means, it means that when we initialize the double underscore init method inside the child
class, then Python expects for some function to be called intentionally. Now this function is named
super. And what super allows us to do, it allows us to have full access to all the attributes of
the parent class. And by using the super function, we don't really need to hard code in the attribute
assignment, like we have done with the name, price and quantity. And as well as for the other
validations that we have executed every time that we want to come up with a child class.
Now imagine how hard that is going to be. If for each of the child classes that we will create
in the future, we will have to go through copying and pasting assert price and quantity. And as
well as doing the assigned to self object thing in those three lines. That is going to be a lot of
duplication of code. Now to save us that time, that is exactly why we needed to use the super
function, the super function will allow us to have the attributes access from the parent classes.
And therefore, we will be able to fully implement the best practices in inheritance when it comes to
object oriented programs. Now again, this program works because we assign the attributes of name,
price and quantity for the sales object in the trial class. But if I was to remove those three
lines, and as well as those two lines, now those lines are happen to be the lines that I have
copied and pasted and try to run this program, then you can see that we receive attribute
error phone object has no attribute price, and pay attention from what line it comes from.
It comes from line 21 from the item class, because it thinks that it has the attribute of
price. But we never have the price attribute in the phone level. Because we just deleted the
self dot price is equal to price. And that's why now we have some problems. And we are going
to replace all the lines that we have deleted with the following thing that I'm going to just
execute now. So I'm going to go to the first line of our constructor, and I'm going to say call to
Super function to have access to all attributes slash methods. And then I'm going to say super,
net I'm going to open up and close parentheses. And then I'm going to use the double underscore
init method like that. Now you can see that the second that I have completed this, then there
are no more warnings about the constructor in this child class. And you can also see that these
double underscore init method expects for some special arguments. Now those special arguments
obviously coming from the item class that we inherit from. So if I was to pass in here, name
and also price and also quantity Then this should be fine. Now, you can also ask yourself isn't
the duplication of code, the fact that we also copied and pasted the parameters that we receive
in the child class. And yeah, that is a perfect question. That is something that could be solved
by something more advanced. If you heard about keyword arguments, that is something that we can
solve it with that way. And then we will not have to duplicate the parameters that will receive for
the constructor, that is not something that I'm going to show for that stage, I'm going to stick
with it. And I'm just going to leave it as it is now calling the super function. And as well
as the init method right after it should be responsible to have the same behavior like we
had previously. So we should still see 2500 for this print line, and we should not see
any arrows. And if I was to run the program, then you can see that we receive the expected
result. So that way, we implement the best practices of object oriented programming for each
child class that we use a separated constructor, we also are going to need to call the super
function in order to have fully access for all the attributes and methods that are coming from
the class that we inherit from. Alright, so I minimize the code for our classes. And I
also left with one instance of foam here. Now I want to show you the results of the following
things. So I will say print, and I will see what is the list of all in the item class is going to
bring us back. So I'm going to say item dot all. And then I'm also going to say phone that all if
you remember, we implemented this class attribute as well here. So I will minimize the code back.
And then I will run our program. Now you can see something very weird in here we see item. And then
we basically see the result of the array PR method that comes from the item class. Now the reason
that this happens, because we never implemented in our EPR method inside the form class. So that's
why we see this on generic result of item. Now you can also pay attention that we only create an
instance of the phone class. So that's not so good that we see item in those outputs. So what
we call use, instead of hard coding in the name of the class in the rppr method inside the item
class, then we go to access to the name of the class generically. Now if I was to replace this
with some special magic attribute that will be responsible to give me the name of the class, then
this will be perfect. So I'm going to delete that. And I'm going to use curly brackets, and I'm
going to say self, dot double underscore class, dot double underscore name. So that is a
generic way to access to the name of the class from the instance. And by doing this, then besides
receiving item, hard coded string, then I should receive the name of the class that I initialize
from the very beginning. So this should be phone, because that is the only single instance
that I have right now. And you can see that this is exactly the result that I'm receiving back.
So that is perfect. Now I said earlier that by using the super function, then we basically have
access to all the attributes and the methods that are coming from the class that we inherit from. So
what that means, it means that we will also have the access to the class attribute of all that is
inside the item class. And I'm talking about that attribute, right. Now to show you that, then I'm
going to open back the code from the form class. And I'm going to remove the old attribute.
And I'm just going to do that right now. And I'm also going to delete the actions to execute
where I use form dot all dot append, because we no longer having the old attribute in the
form class. And if I was to remove those, and execute our program now, then you can see
that I still receive the same result. So that is a great idea removing the old attribute in the
child class level, it is a great idea to only use the old attribute in the parent class, because
by using the super function in the child class, we will have access to the old attribute. So
this means that if one day we'd like to have access to all the items instances that have
been initialized, including the child classes, then accessing them from item dot all should also
be enough. Now you might be confused how this line is responsible to add this instance inside the
all attribute that is happened to be a list. And that's happening because by using the super
function and as well as the in it, then we basically call the init method inside the parent
class. Now in the latest line inside this method, we also use item dot all dot append, which is
also going to be accessible From the form class, so that's why calling the all class attribute
from the item class is a better idea, because it will give us the complete picture. Okay, so
before diving into the topic of that episode, then we're going to need to do some code
organization in here, because as you can see, for each of the child classes that we will go
ahead and create in the future to extend this project, then we're going to need to do this in
the main.py file, because that was the only single file that we were working with. And now that
our project grows, we need to start working with multiple files. So that's why it may be working
with a file that will represent the class of item and working with a separate file that will
represent the foreign child class will be a better idea. So we will have the main.py file dedicated
for only creating instances of those classes. So let's get started with this. So I'm going to go to
the project directory and create two Python files. First one, we will name the item.pi.
The other one should be named phone.pi. And I'm going to take the code from our item
class. And I'm just going to grab everything. Why, while the following, and I'm going to cut this and
then I'm going to paste this in inside of that. Now pay attention that I use the CSV library.
So that's actually the location that I need this library. So I'm going to just copy the import
line. And that should be good enough. Now I'm going to do the same process for the form dot p y,
I'm going to be copying this into the form.py file as well. But now this file needs to import the
item class because as you can see, we got an arrow here. So we should say from item, import items
like the following in the arrows should be gone. And then in the main.py file, we can basically
use this file to only instantiate instances, meaning creating data that will represent
something to Python. So this means that we can go ahead and import the class from
the item file, we can do the same from the form file. And then we can go ahead and do the
stuff that we use to do so we can say item dot instantiate from CSV. into verify that this works,
we can also say print, and item dot all like that. And if we want to run this file now to see that
this works, then we can do that. And you can see that everything works just as expected. Now just a
quick side note, I'm not going to rely too much on the child class that we have created in the latest
episode, to show the problems that we're going to solve in that episode, I'm going to rely more
on the item class so that it will be easier to follow. And we will not complex things too much.
Now that doesn't mean that I do not recommend using child classes or something like that. But it
will be just easier to show you the cases that I'm going to show in the parent class. So that's
why For example, I deleted temporarily the input line of the form class. And I just came up
with a random item instance that name is my item in the price happened to be that number I did not
specify quantity because we have a default value. And now after this line, you can see that
I override this attribute by the string of other items. Now the expected result is not
going to surprise anyone because we see at the right time when we print this attribute. But we
might want to ask ourselves is that the behavior that we always want? What if we want to restrict
our users to change the attribute of name, once the name has been set up in the initialization,
meaning in that line? Well, that's something that we might want to achieve for critical attributes
like the name of your instances, and in our case, the name of our item. So what we could do, we
could actually go ahead and create read only so called attributes, meaning that we have only one
opportunity to set the name of our item. And then we cannot touch the value of that anymore. So what
that means it means that we can set up this in the initialization. And we should have arrows if we
try to override the value of that. Now that's also known as encapsulation when we talk about
the principles of object oriented programming, which I will be focusing more on the future
episodes. But now let's go ahead and see how we can come up with read only attributes how we
can restrict our users to override the attributes after the initialization of our instances. Okay,
so on the left side we have the main.py file, and on the right side we have the item.py file which
we are going to work on and inside the class I'm going to create our first read only attribute. Now
the way that you can start doing this is by first using a decorator and if you remember from
the previous episodes decorators are like functions that you can pre execute before another
function. So I could go ahead and use the property decorator, and then go ahead and create a
function. And here is the exact location that I could set up the name of our read only attribute.
So for testing reasons, let's go ahead and call this read only name something in that time,
all right, and then I will open up and close parentheses, and this will obviously receive self
because it's going to be belong to each of the instances. And now for testing purposes, let's
only go ahead and return a random string like a three times. Alright, and then now that we have
done this, I can go to our main dot php file and try to access this property. Now pay attention
that I'm going to call those properties and not attributes. So I'm going to go here, and I'm going
to try to print item one, that name and now that I have wrote name, pay attention to the differences
in this drop down for read only name, we receive a totally different icon here on the left side,
which stands for a property where in here we see the flutter which stands for irregular field. So
if I was to try to print that and run our program, then obviously we will receive the expected
result. But if I was to try to set a new value, for the read only name, say that we want
to change this to something like that, then you're going to see that Python is going to
complain about this. And even if we try to execute that, then we will end up with an exception that
says attribute error can set attribute. So that is how read only attributes, so called are working in
Python, you can create those by using a property decorator before your functions and return
whatever value you'd like to return. Now the biggest challenge here is going to be converting
the name attribute that we actually have, which is happened to be exactly here into obeying
a read only attribute. And that is going to be a little bit challenging. But let's go ahead and
start working on that. So first things first, I'm going to delete those three lines, because
we are not going to use this property anymore. And I'm going to scroll up a bit and
work underneath this constructor here. So you might think that converting the name attribute
into being read only meaning a property is as easy as doing something like first using the property
decorator, and then go ahead and say def name, then receive self as the parameter. And then use
something like return self dot name, because we already have the self type name assigned to the
self object. But actually doing something like this is like saying to that class, hey, from now
on, you're going to have a name attribute that is going to be rain only. And that is straightforward
the effect of the property decorator. So I'm going to leave a comment here that is going to look like
the following. But if you remember, we try to set the self dot name into a new value inside our
constructor. So you can see that this action is illegal because we have a read only property here.
So when you go ahead and create a property with the name of basically name, then you are
normally allowed to set this value anymore, you are only allowed to have access to see
this back in whatever instance you will create. So that is why if I was to hover my mouse here,
then we're going to see an arrow that is saying property name cannot be saved. So the pythonic
way to doing this workaround to get over this is using one underscore before the name of our actual
attribute name that we assign to the self object. And by doing this, we earn a couple of things that
are quite important. So first, let me add here and underscore and just use something like that. And
then now I need to go to my property function, meaning the property attribute. And I'm going to
need to add here the double underscore as well. Because First things first I go ahead and
set up the value for my double underscore, excuse me single underscore name into being equal
to the value of this parameter here. And then I go ahead and use one more read only attribute that I
intentionally give the name of name and I and then I return self dot underscore name. Now I can go
back to my main.py file and see what effects those lines are having right now on our instances. So
first, I can go ahead and set a name for my item. And I can access to the name of this item by
saying something like I didn't want that name. So I don't really have to go ahead and use item
one dot underscore name, because that is going to be a little bit ugly, and not convenient. Because
accessing attributes with always an underscore before is not nice for each of the instances that
you look to access to the attributes. Doing this one time inside the class is going to make it
okay. But trying to access those attributes outside of your class, meaning from the instances
is not going to turn it into too much pretty. So that is the best way to overcome such a thing. And
now if I was to try to print that, then, excuse me, let me fix that quickly by item one dot name,
and run our program, then you can see that that is working. And now let's go ahead and also see if
we can set our name into being equal to another thing like that, see, if that works, I can see
that I cannot set that attribute. But how ever, I can still see these underscore name from the
instance level. And that is maybe something that you look to avoid, it could have been a
lot nicer if we could somehow prevent totally the axis from this underscore naming here. So
the way that this is achievable, is by adding one more underscore to the attribute name. Now,
this might remind you something that is called private attribute. If you're familiar with
programming languages, like Java, or C sharp, that is pretty much the same behavior of using
the private key word before your attributes in those kinds of programming languages, where it
has different principles when it comes to object oriented programming. So to sum up, if you add
one more underscore to your attribute names, meaning you use double underscore, then you
basically prevented the access to those attributes totally outside of the class. So let's see a
simulation of that. So I'm going to minimize the terminal, and I'm going to go to my item.py
file. And besides using here, single underscore, I'm going to add one more. And then I'm going
to change this to double underscore as well. And now if we were to go to our main.py file, and
let's use here it one dot and try to basically use double underscore and try to access to name now we
can see that I don't even have an auto completion from my drop down, because I don't have access to
see this attribute from the instance level. And that is something that you look to achieve when
you want to have a clean read only attribute. And that is the way that you can do that. So
if I was to try to print this, then that's just going to complain about how it does not
have the attribute of double underscore name in this instance. And again, if I was to remove
those double underscores, then I will just access it as a property meaning as a read only attribute.
And that is exactly what I looked to have here. Alright, so now that we got the idea of that,
then we still might be curious about how to set a new value for the name attribute.
Now obviously using the property decorator is going to turn this into being a read only
attribute. But there are still some decorators that will allow you to however, set a new
value for this property of name. So let's see how that is achievable. So obviously, that
is not going to work. Because it says can't set attribute. So what we can do is we can use a
new method, where we can declare that we like to also set a new value for this attribute that we
named name. So the way that that's going to work is by going to our class here. And using here
one more method with a special decorator. Now this decorator is going to look like the
following. So I'm going to refer to the name because that's the property name. And then
I'm going to use the dot setter. So by doing this, then I basically say, hey, so I still want to
set a new value for that name, although that is a property meaning a read only attribute. So
now if I was to go down and say something like def name, and this will receive self and as well
as one additional parameter because the additional parameter should refer to the new value that
I want to set to that name. So I will receive a parameter that I could name something like
value. And then inside here, I'm only going to set the new value for our double underscore name.
Because if you remember, when an instance tries to see the value of name, then we basically return
self dot double underscore name. So when a user will try to set the name again to a new value,
then it should execute self dot name equals to value and by doing this, I basically allow our
users to yet set a new value for name. So now let's show what effect those three lines are going
to have in our main.py. You can see that now the arrows gone, I can now go down here and use print
item one dot name. And that's going to work I can see that I have other item. So this means not only
I can set a new value for my underscore name, so called in the initialisation, I can also do that
later on, if I only use this convention in here. Now those getters and setters thing are always
confusing in normal programming language you work. So I will do a final summary of all what we have
learned until this point. All right. So using add property will basically give you a control of
what you'd like to do when you get a an attribute. And also by using this then you basically convert
this attribute into being read only if I was not implemented these setters in here. So you
can see that now, when I commented those out, then this line is going to have some problems,
because by not doing this, then I basically say that hey, name is read only, you cannot set
that if I was to again uncomment those back, then I will have the control to set this
attribute to whatever attribute I'd like to now by using this statement here, basically
getting the attribute, then I basically call the bunch of codes that are being executed in
here. So whenever I use item one dot name, then Python says to itself, okay, you try to get that
attribute. So, I will go ahead and try to execute all the lines of codes are that are here. So
that is what exactly happening here. And to show you that, then I can just use a random print
function here that will say, you are trying to get name like that, then you should see this line
being printed right before what the actual value is. Because at first, we print you're trying
to fit the name, and then we return the self dot underscore name, so it prints that over
here. So that is what happening when you try to get an attitude. But when you try to set an
attitude, then Python says to itself, okay, so here, you try to set an attribute. So because
you set an attribute, then I should go ahead and execute the code that is inside here, because
that is the center of that attribute. So that is why when you go ahead and use this decorator,
then you should always receive a parameter because the other item is going to be passed
as an argument to that parameter, it is very important to understand that. And that
is why I can only allow myself to use one line of code that will say self dot double underscore name
is equal to the new value that you try to set. And to show you that again, I can go here and say
print you are trying to set in this line should appear just before this print line because at
first I tried to set a different value for name, and then I just print it back like that. Okay,
so if I was to run that, then you can see that at first we see the line of you're trying
to set then right after it we actually see whatever item one dot name is equal to. Now
the reason that the value is set is because we set it over here and then the next time I try
to get the value, then those lines are getting executed. So that is the lifecycle of getters
and setters. And that is the way that it works. By having the control of whatever you'd like to
do when you set a new value, you can also restrict it, you can go ahead and do some conditioning,
or you can go ahead and raise some exceptions. If you don't like the value that you receive,
let's say that you want to restrict the length of the characters for the name of that attribute.
Alright, so that is something that you can do, you can actually go here and say if when of
the value is greater than 10, then you'd like to raise exception, that will say something like
your, the name is too long, something like that. And then you can say else and then you can
execute the line that will be responsible to set that value. So intentionally I'm going to
leave it as it is because the length of that is nine characters. So we should not have any arrows.
But if I was to add here, two more characters, Like that, and executed, then you can see that we
are going to receive an exception that will say the name is too long. So that's how getters and
setters are working in Python, you will now have all the knowledge that you need to play around
with different attributes, and manage them the way that you would like to. So I believe that after
the information that you received in that episode, you have everything that you need to manage your
attributes successfully and play around with it, as well as coming up with rich classes that will
have multiple attributes. And then you can set up special behaviors to those attributes. And also
you can decide that you will not like to force to receive those attributes in the constructor,
you can decide that you can delete some parameters in your constructor. And you can say that,
you will not like to assign those to the self object immediately when you create an instance. So
whatever you would like, do you have all the tools to play around with how to manage your attributes,
object oriented programming comes with four key principles that you should be aware of. So you
will understand how you should design your large programs, so it will be easier to keep developing
it. And I'm going to talk about those principles which are encapsulation, abstraction, inheritance
and polymorphism. So the first principle will be encapsulation. And we will talk about this a
little bit. So encapsulation refers to a mechanism of restricting the direct access to some of our
attributes in a program. Now notice how we did a great job in the last part, where we implemented
the encapsulation principle on our project. So pay attention to how the name attribute could
not be set to a new value, before it goes through some conditions that we set here, like the length
of the character being less than 10 characters. So restricting the ability to override the values
for your objects within your saddles, is exactly what the encapsulation principle is about. Now to
even give you a better example of encapsulation principle, then we are going to apply similar
things to an additional attribute, which is going to be the price attribute. Now if you take a quick
look in that program that I have just executed, then you can already see that I have the ability
to directly set these objects into whatever number that I like to also negative 900 will work here.
So that's probably something that we look to change. And the way that we can change that is by
implementing some methods that will restrict the access to this price attribute. So it could have
been nice if we could have two methods that will be responsible to increment these price by some
percentage amount. And the same goes for the discount. Now if remember, we already came up with
a similar method that looks like apply discount when we talk about class attributes, because
self dot pay rate multiplied by the actual price is actually going to change this attribute being
decreased by 20%. Because pay rate is set to 0.8, if you remember from the previous episodes, so
let's go ahead and continue designing this price attribute to totally support the encapsulation
principle. So first things first, I'm going to convert this prize into being a private attribute.
So it will be a great start avoiding to set this price directly like we have seen previously. Now
I'm not going to just add here double underscore, besides I'm going to grab this whole
thing. And I'm going to right click, and then I'm going to say refactor, rename,
and then I'm just going to rename this price by setting it like that double underscore before that
now doing this is actually going to refactor this change on the entire class where we try to access
the price attribute. So that is a great thing. So we don't really have to change everywhere. So once
I have done that, then if we will also take a look in the apply discount, then you can see that
we have this being set to a new variable name that we came up with. So now that we have done
this, then let's go ahead and create a property. So we will have the ability to access the price
attribute temporarily being only read only. So I'm going to say add property. And then I'm going
to say def price, then I'm just going to return self dot price. So that's a great starter to
restrict the access to the price attribute, because now we still have access to the price
attribute. And then we cannot set that. So you can see that if we were to try to access item one dot
price, we will have some errors, but we can just access the actual value of that where it
comes from the initialisation. So that's a oh actually I see that we hit an error that says
recursion error and that's probably because I did not add the double underscore in here by mistake.
So that's actually a great exception that we came across right now, you can see that we are going
to hit recursion error, maximum recursion depth exceeded. And that happened because I tried to
return the price without the double underscore. So if we try to call the self dot price, then it is
going to try to call this function. And if we try to return that, then it's just going to loop over
it again. And in some time, it's going to fail with the recursion error as you see. So that's
actually a great exception that we see. Now, if you see this exception in your object
oriented programs, now you know how to handle it. And if I was to come back now to Maine and execute
that, then you can see that the expected result is here. Alright, so now that we have done this,
then let's go back to our class and try to work on our methods that will modify the attributes
of double underscore price. So I will actually cut this metal from here. And I will just put
that right under the property price that we came up with. So we will have a cleaner look. Now
First things first, you can see that we have the apply discount. And we will also like to come up
with a ply. increment like the following. And we will like to say here, self dot double underscore
price is equal to self dot level underscore price plus self dot level underscore price multiplied
by some value that we can receive as a parameter. So we could actually receive a parameter that
we could name implement value, and then we could just multiply it by that number. So now that we
came up with this, then let's test this up. Okay, I'm going to go back to our my main.py. And then
I'm going to call it one dot apply increment, and then I'm just going to pass in 0.2. So we
will increment the value of 750 by 20%. Now the next time that I access the item one dot price, we
should be able to see the actual value of price, which should be 900. And if I was to run
that, then you can see that the price has been incremented to 900 as expected. So that is exactly
encapsulation in action, because you go ahead and you don't allow the access directly to the price
attribute. Besides you modify this attribute by using methods like apply increments, or
apply discount. Now the same will happen if I was to now go ahead and use item one dot apply,
discount, and you can actually modify this method in the way that you'd like to. But this currently
refers to a class attribute that we use here. So this should also again, apply a discount of 20% to
the 900 price, we should be able to see 720. And that's exactly what is happening here. Alright, so
the next principle that I'm going to talk about is called abstraction. Now abstraction is the concept
of object oriented programming that only shows the necessary attributes and hides the unnecessary
information. Now the main purpose of abstraction is basically hiding unnecessary details from
the users. Now by seeing users, I basically mean people like me or you that are going to use the
item class to create some item objects. Now we can see that now we have a new program here that has
one item object that it's name is my item price being that number, and we have six from this item.
Now we can also see that I came up with a method that doesn't really exist, which is called send
email. So that method at the end of the day should send an email to someone that would like to decide
about this item. And it will send info about how much money we can earn by selling all the items
and maybe about some more info that is related to this item. Now sending an email is not as
easy action just by calling it with that way. Because in the background, email sending has to
go a lot of processes like connecting to an SMTP server. And as well as preparing the body of the
email with an auto message that will display some info about this item. So as we can understand
we have to go through a lot of new methods before we go ahead and just call a Send Email
method. So to simulate that, then I can actually go ahead and say send email. So I will just create
the method that is necessary. Temporarily I will use pass. Now as I said we also have to go through
a lot of other processes. So it is a great idea to create methods for each of those processes, like
connecting to an SMTP, server SMTP server like that. And I will just say pass because we only
try to simulate the idea of abstraction here, I'm not really going to send an actual email to
someone, I'm just simulating an idea of sending an email. And we will also have to go to preparing
a body for an automatic mail prepare body, right, and then I can just return a formatted
string that will say something like hello. Someone, we could receive this as a parameter as
well. And then we can say, we have self dot name, six times, right, so it should be six. So
quantity times like that, then I can say regards to shave. So that is just a very simple
email that we can send to someone. Now we can understand that we have to call those methods
inside the same email. So simulating that will be self dot Connect, and then self dot, prepare body.
And probably we also need to go through sending it right, so we can just say something like send
here, then us passing it. Now you can see that those metals at the end of the day are only
going to be called from the Send Email. Because those are just parts of the email
sending process, that is a bigger process that we divided into multiple steps in this
class. Now the biggest problem is, we will have access calling those methods from the instance.
And that is exactly what abstraction is about. abstraction principle says to you that you should
hide unnecessary information from the instances. So that is why by converting those methods into
being private methods, then we actually apply abstraction principles. And that is achievable
in Python in a way, which I'm going to be honest, is not too much convenient, but it is achievable
by adding double underscore. Now again, in other programming languages, this is achievable
by having access modifiers for your methods, like private or public. And I'm talking about
programming languages like Java, C sharp, etc. So if we were to convert those methods to
private methods, by only adding level underscore, then those only cool to be cold from the class
level, meaning inside the class. So if we were to try to access it, then you can see that I am going
to have auto completion, meaning I will have the ability to access those methods. And then I will
just do the same for the other methods. Now this arrow comes from here, because we did not really
specify some argument, I'm just going to add an empty string. Now if I was to go back to our
main.py file, then you're going to see that we are going to have some troubles. Even if I'm going
to try to access it with a double underscore, I'm not even going to have an auto completion.
And the reason for that is because that is a private method. So you really have to think
about your methods if you want to make them accessible outside of your class, meaning from the
instances. And that is exactly what abstraction is about. You want to abstract the information
that is unnecessary to call it or to access it from your instances. Okay, so inheritance is the
third principle of object oriented programming. inheritance is a mechanism that allows us to reuse
code across our classes. Now, that's something that we have totally designed well throughout this
course, because we came up with more classes that are child classes of the item class, where each
child class represents a kind of an item. Now pay attention how I change the import line from phone
import from, and I use the child class of item, which we came up with, which is called the phone,
you can see that I'm passing similar arguments in they can still use a code that is implemented
in the parent class. If we were to execute this program, then we are not going to receive any
problems. Because phone uses all the methods and the attributes that it inherits from the
item class, which is exactly here. And if we remember we designed the Send Email method
in the parent class and we can use it from the instance of a phone and we can also do that
for the rest of the methods that we came up with that really affect some of the attributes like in
the interview. capsulation part where we applied the apply increments method that receives an
increment value. And if we were to test this incrementing, the price by 0.2, and then
printing item one dot price, then we should see 1200. So if we were to print that, then
you can see that that is exactly the result. So that is mainly what inheritance is about. It
is about reusing the code across all the classes. And that's exactly the scenario here. And the
beauty behind that is that you can come up with more child classes that will represent the kinds
of items like laptop, keyboard, camera, everything that you will think about. And then you can just
modify specific methods that you'd like to calling to the kind of item that you have. So that's
perfectly describing what inheritance is about. Alright, so the last principle that we have now is
polymorphism. Now polymorphism is a very important concept in programming. It refers to use of a
single type entity to represent different types in different scenarios. Now, a perfect example
for this definition, will be some of the functions that we already know that exists in Python. Now
just a quick side note, polymorphism refers to many forms Paulie being many, and morphism being
forms. So again, the idea of applying polymorphism on our programs is the ability to have different
scenarios, when we call the exact same entity and an entity could be a function that we just call.
Now, as you can understand polymorphism isn't something that is specifically applied to how you
create your classes. That is actually something that refers globally to your entire project.
And in the next few minutes, we are going to see some bad practices where polymorphism is not
applied. And we're also going to see where in Python the polymorphism is perfectly applied. So
a great example of where polymorphism is applied, is in the lane built in function. Because the land
building function in Python knows how to handle different kinds of objects that it receives
as an argument, and it returns you a result accordingly. So as you can see in here, if we
were to use the Len built in function in a string, then we will have received the total amount of
characters. But if we will do this in a list, then we will not receive the length of characters
of this entire expression in here. Besides, we will receive back the amount of elements
that are inside this list. And to show you how this is going to work, then I'm just going to run
this program and for sure, the results are just as expected. So as the definition of polymorphism
says, it is just a single entity that does know how to handle different kinds of objects, as
expected. All right. So now that we understood that the polymorphism is applied everywhere in
Python, especially in the land building function, let's also understand where it is implemented on
our project here. Now we can see that I tried to call the apply discount method that is never
implemented inside the phone class. And the fact that I can use it from the item class,
it is because we inherit from this class. And that is the basically reason. Now if I was to
go back to that main dot php file and run this, then you can see that that is going to work
because the apply discount is a method that I can use from the inherited item class. Now
that's exactly what polymorphism is also inaction. Because polymorphism again, refers to one single
entity that you can use for multiple objects. Now, if I was one day to go ahead and create
more kinds of items, meaning more classes that will represent different kinds of items,
and from them to call the apply discount method, that's also going to work because the apply
discount is a method that is going to be accessible from all the kinds of objects so that's
exactly why you might have heard about the terms of inheritance and polymorphism together combined.
Now to show you that, then let's try to create one more class that is going to be identical to the
phone class, right, I'm going to create a keyboard file. And then I'm just going to say here, class.
You know what, before that, let's go to phone and copy everything from here and paste this in like
that. I'm going to get rid from those lines. And I'm just going to leave the init as
it is I'm going to change the class name from phone to keyboard and I'm also going to
delete that attribute that we don't need broken phones. Alright, so now that we have this, then
I can actually go ahead to my main dot php file and use one more important line that
will say from key board import keyboard, and then I'm going to change this to keyboard will
replace this name, just to make it more realistic, then I'm going to run the same problem, you can
see that this works. So that's again exactly where polymorphism is in action, because we can use this
single entity from different kinds of objects, and it will know how to handle it properly. Now by
saying handle it properly, then I basically mean, you can have the control of how many
amount of discount you want to apply inside your classes now, because if we were
to go to keyboard and use a class attributes, exactly like we used in the item class, which
was pay rate, then we're going to have full control for all the discounts that are going
to apply to the keyboard. And to show you that I am going to attempt the typing in pay rate.
And you can see that I even have auto completion because overriding in the child class that is
legal, alright, so I can just say pay rate is equal to 0.7. And that will be it. Now I have
the same discount amount for all my keyboards. If I was again to run the main.py file, then you
can see that the results are just as expected, we see 700. So that is the beauty behind inheritance
and polymorphism together. And the same will go for sure if we were to decide that we would like
to have 50% discount. So it will only take for me to go ahead and say pay rate is equal to 0.5.
And that's it. So I hope that you understand about polymorphism a bit better now. Now just a
quick side note polymorphism is perfectly cool, we implemented by using abstract classes. And that
is just the identical way of using interfaces in other programming languages like Java interface is
a way that you can implement how a class should be designed. Alright, so it is like a template
for a class. And that is achievable by using abstract classes, which I'm not going to cover in
that part. But again, polymorphism, like I said, is a term that is implemented in different
areas in the whole python programming language. So I hope you had a great time learning
the object oriented programming course. Now you have a lot of tools that you can go ahead
and try to implement by yourself on your projects, which will really help you to take you to the
next step as a developer. See you next time.