Object Oriented Programming with Python - Full Course for Beginners

The Power of Inheritance and Polymorphism in Object-Oriented Programming

In object-oriented programming, inheritance is a fundamental concept that allows for the creation of new classes based on existing ones. This technique enables developers to create a hierarchy of classes, where a child class inherits the properties and behavior of a parent class. By doing so, developers can reuse code, reduce redundancy, and increase code maintainability.

To illustrate this concept, let's consider an example. Suppose we want to create a program that simulates a store with different types of items, such as phones, keyboards, and other electronic devices. We can start by creating a parent class called "Item" that has attributes like price, quantity, and discounts. The Item class will serve as the base for our child classes.

Next, we'll create a child class called "Phone" that inherits from the Item class. This class will have its own set of attributes specific to phones, such as screen size and processor speed. The Phone class can also override some methods of the Item class, like the applyDiscount method, to provide specialized behavior for phone items.

If we want to create another type of item that shares similarities with phones but has different characteristics, we can create a child class called "Keyboard" that inherits from the Item class as well. This class will inherit all the attributes and methods of the Item class and add its own specific attributes like key count and keyboard layout.

Now, let's go back to our main.py file and use an instance of the Keyboard class in the applyDiscount method. As we can see, the applyDiscount method is accessible from all types of objects because it's a method that belongs to the Item class, which is the parent class of both Phone and Keyboard classes.

Inheritance and polymorphism are closely related concepts in object-oriented programming. Polymorphism allows us to use a single entity from different kinds of objects, and it will know how to handle it properly. In our example, we can use the same applyDiscount method on an instance of either the Phone or Keyboard class, and it will work correctly.

This is where polymorphism comes in. By using inheritance, we can create a hierarchy of classes that share common attributes and methods. Polymorphism allows us to treat objects of different classes as if they were of the same class, which is useful when we want to perform operations on objects that have similar characteristics.

In our example, we've shown how we can use polymorphism with inheritance to achieve flexibility in our code. We can create multiple child classes that inherit from a parent class and override specific methods or attributes as needed. This allows us to write more generic code that can work with different types of objects.

One of the benefits of using inheritance and polymorphism is that we have full control over how many discounts are applied to an item, regardless of its type. By using interfaces in other programming languages like Java, we can achieve a similar effect by defining a template for a class that must be implemented by child classes. This approach provides even more flexibility and allows developers to define their own rules for interacting with objects.

In conclusion, inheritance and polymorphism are powerful tools in object-oriented programming that allow us to create flexible and maintainable code. By using these concepts, we can write programs that can work with different types of objects and provide a high level of abstraction and reusability. As developers, having a solid understanding of inheritance and polymorphism will help us take our skills to the next level and tackle complex programming challenges with ease.

The Benefits of Polymorphism

Polymorphism is a fundamental concept in object-oriented programming that allows us to treat objects of different classes as if they were of the same class. This technique provides flexibility and reusability, making it easier for developers to write code that can work with different types of objects.

In our example, we've seen how polymorphism allows us to use a single entity from different kinds of objects, such as phones and keyboards. By doing so, we can write more generic code that works with different types of objects without having to know their specific characteristics.

One of the benefits of using polymorphism is that it allows developers to have full control over how many discounts are applied to an item, regardless of its type. This means that we can define our own rules for interacting with objects and provide a high level of flexibility in our code.

Another benefit of polymorphism is that it provides abstraction and reusability. By using polymorphism, we can write code that works with different types of objects without having to know their specific characteristics. This makes it easier to maintain and update our code over time.

Using Polymorphism with Abstract Classes

Abstract classes are a powerful tool in object-oriented programming that provide a way for developers to define a template for a class that must be implemented by child classes. By using abstract classes, we can achieve a similar effect to polymorphism without having to use inheritance.

In our example, we've seen how we can create an abstract class called "Item" that defines the interface for all types of items. We can then create child classes like Phone and Keyboard that inherit from the Item class and implement their own specific attributes and methods.

By using abstract classes, we can define a set of rules or behaviors that must be implemented by child classes. This allows us to provide a high level of abstraction and reusability in our code without having to use inheritance.

In conclusion, polymorphism is a powerful technique in object-oriented programming that provides flexibility and reusability. By using polymorphism with abstract classes, we can achieve a similar effect without having to use inheritance. This approach provides even more flexibility and allows developers to define their own rules for interacting with objects.

"WEBVTTKind: captionsLanguage: enIt'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.\n"