Python-OOP-Tutorial1

In this project, I want to use python classes in order to construct an emaginary box. For simplicity, I assume that the box can be specified using two points: the point at top-left of the box, and the other point at the down-right of the box. Also, in the second part of the project, the goal is to use the class Box to define a subclass with some extra features. This project, enables us to practice:

The first step: defining class Box

As I mentioned, the box in our example must be represented using two points of top-left and down-right.

So we define a class named Box, with an initialising method who accept the coordinates of the points. However, suppose after creating the class, someone else wants to use it, and they do not have any instractions on how they need to pass arguments to the class. For instance, they do not know if they should pass coordinates simply one by one like Box(x1,y1,x2,y2), or if they should put coordinates of each node in tuples as Box((x1,y1),(x2,y2)).

In order to avoid such kind of problems, I use the prefix asterics operator to enter arbitrary number of agruments.

The other important thing to consider is that the user may not have any clue about the order of entering the coordinates; they should first enter the coordinates of the top-left point or the down-right point. We can avoid such kind of conflicts by obliging the user to enter the arguments, by specifying their names. For instance, the user should enter x1 = 2 ,y1 = 1, x2 = 3, y2 = 4 or p1 = (2,1) , p2 = (3,4). This can be done by using prefix operators ** in the definition of __init__ method of class Box. It declare to the __init__ method that there are an arbitrary number of arguments that can be entered with their names. In fact, the prefix asterisk operator, do a kind of unpacking process over the data entered to the function. For more information about it take a look at the unpacking concept in here.

In the snippet below **coordinates signals the __init__ method that there are an arbitrary number of arguments which are accompanied by the keywords. As we know, the __dict__ dictionary is responsible to keep track of all of the attributs of instances of class, so one way to specify the attributes of the class is to update the __dict__ for each instance which is created. This is why I have used self.__dict__.update(**coordinates) in the init method.

Note that, in the code **coordinates represents that there are arbitrary number of keyword, arguments that can be entered as arguments, and they will be unpacked in a dictionary. For instance, if we initialize the instance box using box=Box(x1=2,x2=3,y1=1,y2=4) it creates the dictionary coordinates={'x1': 2, 'x2': 3, 'y1': 1, 'y2': 4}. So, in the first conditional statements, when there are four arguments we assume that their keywords should be x1,x2,y1 and y2 as indicated in keyorder. I do the same for the case that the number of entered arguments are two: len(coordinates)==2.

Since the main idea is to represent the instances with a specific order of coordinates (for instance when there are four arguments first I want to represent x1,y1 and then x2 and y2), I have costumized the sorted method of the dictionary using the lambda expressions in order to sort the entered data based on the predefined order of arguments in keyorder.

In order to have a nice representation of the instances of type Box, I also define the special method __str__ which is recalled when we use the str function or print function with the object.

In the following I have defined two instances of Box one using four argument and the other one with two arguments of type tuple.

To improve our class Box it would be nice to define an attribute for the area of the Box. This attribute should have be defined based on the arguments that are entered as arguments to the __init__ method. To implement this attribute, I am going to use descriptor protocols.

In the above snippet, the class Areagetter is the descriptor which is used to define the attribute Area in the class Box. In below, I have defined another instance of Box, and I use the attribute Areato get its area.

As you can see, the area of the box b is 5.

Note that in the definition of __set__ method in descriptor, I have raised an error because it is not logical to put a value to the attribute Area without changing the coordinates. As you can see below assigning a value to attribute Area, it rases an error.

Third step: Inheritance

In this step the idea is to define a simple subclass of the Box class, which may have one extra attribute text. The code that I have written below, take care of the existance of the text attribute in the arguments entered to the class, and act accordingly.

As you can see, know we face a problem. The problem is that b_text inherit the attribute Area, however it does not compute anything. However, when we define am instance of TextBox without text argument, it canculate the Area correctly.

This problem occures because of the conditional statements in the definition of descriptor. In fact, adding the attribute text means that the length of __dict__ whould be either 3 or 5, while these two cases do not considered in the definition of descriptors. By changing the conditional statements, this problem can be easily solved.