Working With Classes

Before proceeding to build Packages in Python, we would like to discuss classes and how to write classes in our Python Program.

What is a class?

We all have heard about Object-Oriented Programming. Implementation of a Class in any programming language helps us to achieve the concept of the OOPs.  A class consists of functions and data, which works as a blueprint, whenever we need to use the associated function and data in a given class, we create an object, which contains its local copy of function and data. So, we are able to access the function and data using object_name and dot operator.

Why Class is needed when the concept of function is already there?

There are many important concepts we realize through the implementation of classes such as Data Abstraction, Data Hiding, Inheritance, Polymorphism. You need to study Object-oriented programming if you want to go in detail.

One important fundamental: Whenever we import a module, which does not contain any class i.e. a simple python script with function and data. We import the function itself, so any other python program that wants to execute this module at the same time can produce unexpected results. Because two python programs will simultaneously share the resources of the given module.

But in the case of Class, we are free to do so, because class gives access to its function and data only through an object. And each object has its own local copy of function and data. Though some data can be shared between classes if we want to, which is further implementation of the OOPs concept.

How to write a Class in Python and access it?

Before proceeding further: just remember these points.

Writing a class:

Simply imagine you are writing a single function or group of functions using data.

PseudoCode: 
class class_name(object)

   
    def __init__(self, arg1, arg2, arg3, .., .., argN):
        self.arg1 = arg1
        self.arg2 = arg2
        self.arg3 = arg3
        self.argN = argN

    def function_name_1(self):
        print("I AM {} Color".format(self.arg1))

    def function_name_2(self):
        print("I AM {} Color".format(self.arg2))

    def function_name_3(self):
        print("I AM {} Color".format(self.arg3))

    def function_name_N(self):
        print("I AM {} Color".format(self.argN))

Please note: In the above code class, object, self, def, __init__ are reserved keyword and you need to remember these keywords for writing a class.

class_name, function_name_1, function_name_2, function_name_3, function_name_4 can be named anything (based on your choice) in compliance with permitted namespace in Python.

What is the use of self keyword: In the above code you can see self keyword repeating a lot of times. We are creating an object that contains a local copy of data variables, thus we use the self keyword where each object refers to its own data and associated function.

How to access it:

We need to create an object for a given class. When creating an object we can pass arguments to initialize data. Then we will access all the data and function using:

PseudoCode: 
#Creating an Object & initializing with N arguments
first_object = class_name(arg1, arg2, arg3, .., .., argN)

#Calling function_name_1
first_object.function_name_1()

#Calling function_name_2
first_object.function_name_2()

#Calling function_name_3
first_object.function_name_3()

#Calling function_name_N
first_object.function_name_N()

 

Implementation of Calculator Python Program using Class:

Initializing Parameters while creating an object

Code: 
class Calculator(object):

    def __init__(self, a, b):
        self.a = a
        self.b = b

    def add(self):
        return self.a + self.b

    def sub(self):
        return self.a - self.b

    def mul(self):
        return self.a * self.b

    def div(self):
        return self.a / self.b


calc_obj = Calculator(99, 3)
print("Addition result is {}".format(calc_obj.add()))
print("Multiplication  result is {}".format(calc_obj.mul()))
print("Division  result is {}".format(calc_obj.div()))
print("Subtraction  result is {}".format(calc_obj.sub()))

 

Output: 
Addition result is 102
Multiplication  result is 297
Division  result is 33.0
Subtraction  result is 96

Encapsulation, IS-A (Inheritance), HAS-A (Composition)

Encapsulation

Encapsulation: When we want to hide some data from other objects or we want to restrict access to some methods and variables. The concept of Encapsulation helps us to prevent direct manipulation or modification of data. Only, the function has access to private data. We hide attributes by making them private. In Python, we hide it by placing underscore as the prefix for the attribute, i.e single “ _ “ or double “ __“

In the below code, we have demonstrated that private class data can only be manipulated using class functions and not using an object.

Code: 
class Color:

    def __init__(self):
        self.__color_name = "Green"

    def Color_Type(self):
        print("Color Type is : {}".format(self.__color_name))

    def Color_Type_1(self, price):
        self.__color_name = "Red"

c = Color()
c.Color_Type()

# changing the Color
c.__color_name = "Blue"
c.Color_Type()

# Setting color using Function
c.Color_Type_1(1000)
c.Color_Type()

 

Output: 
Color Type is : Green
Color Type is : Green
Color Type is : Red

 

IS-A Relationship (Inheritance)

IS-A Relationship: Salmon is a Fish. It means Salmon inherit properties from a Fish. When a child class inherits the property of a parent class. We refer to it as a Parent-Child relationship.

There are three ways in which parent and child class can interact:

  • Actions on the child imply an action on the parent: Implicit behavior
  • Actions on the child override the action on the parent: Override Explicitly
  • Actions on the child alter the action on the parent: Alter Before or After
Actions on the child imply an action on the parent: Implicit behavior

Implicit actions that happen when you define a function in the parent but not in
the child.

Code: 
class Parent(object):
        def function_parent(self):
            print("I am parent")


class Child(Parent):
        pass

a = Parent()
b = Child()

a.function_parent()
b.function_parent()

 

Output: 
I am parent
I am parent

 

Actions on the child override the action on the parent: Override Explicitly
Code: 
class Parent(object):
        def function(self):
            print("I am parent")


class Child(Parent):
    def function(self):
        print("I am Child")

a = Parent()
b = Child()

a.function()
b.function()

 

Output: 
I am parent
I am Child


 

 

Actions on the child alter the action on the parent: Alter Before or After

The third way to use inheritance is a special case of overriding where you want to alter the behavior before or after the Parent class’s version runs. You first override the function just like in the last example, but now we will use a Python built-in function named super to get the Parent version to call. Here’s the example of doing that so you can make sense of this description:

Code: 
class Parent(object):
        def function(self):
            print("I am parent")


class Child(Parent):
    def function(self):
        print("I am CHILD, BEFORE PARENT is called")
        super(Child, self).function()
        print("I am CHILD, AFTER PARENT is called")


a = Parent()
b = Child()

a.function()
b.function()

 

Output: 
I am parent
I am CHILD, BEFORE PARENT is called
I am parent
I am CHILD, AFTER PARENT is called

Demonstration of super for initialization and function calling from Child class

Using super() with __init__

Code: 
class Cube(object):

        def __init__(self, arg):
            self.arg = arg

        def CubeFunction(self):
            self.CubeResult = self.arg * self.arg * self.arg


class CubeAdd(Cube):

        def __init__(self, arg, arg1):
            super().__init__(arg)
            self.arg1 = arg1
        def CubeAddFunction(self):
            super(CubeAdd, self).CubeFunction()
            self.CubeAddResult = self.CubeResult + self.arg1



b = CubeAdd(2,1)
b.CubeAddFunction()
print(b.CubeAddResult)

b = CubeAdd(3,1)
b.CubeAddFunction()
print(b.CubeAddResult)

b = CubeAdd(4,1)
b.CubeAddFunction()
print(b.CubeAddResult)

 

Output: 
9
28
65

HAS-A Relationship (Composition)

HAS-A Relationship:  A dog is a mammal and Parrot is a Bird. Dog inherits features from Mammal parent class and Parrot inherits features from Bird parent class.  Sometimes, one class does not inherit all the features from a class. But both dog and mammal have a mouth. It means they have some common properties but do not inherit all. So, we achieve this functionality through composition, we directly call a function from another class, without inheriting all the featured from a class.

The following code demonstrate the use of Composition.

Code: 
class Cube(object):

        def __init__(self, arg):
            self.arg = arg

        def CubeFunction(self):
            self.CubeResult = self.arg * self.arg * self.arg


class CubeAdd(Cube):

        def __init__(self, arg, arg1):
            self.arg1 = arg1
            self.cube = Cube(arg)

        def CubeAddFunction(self):
            self.cube.CubeFunction()
            self.CubeAddResult = self.cube.CubeResult + self.arg1



b = CubeAdd(2,1)
b.CubeAddFunction()
print(b.CubeAddResult)

b = CubeAdd(3,1)
b.CubeAddFunction()
print(b.CubeAddResult)

b = CubeAdd(4,1)
b.CubeAddFunction()
print(b.CubeAddResult)

 

Output: 
9
28
65