Object Oriented Programming - Classes and Objects

Object-oriented programming (OOP) refers to a software design practice in which the programmer defines both data type of a data structure as well as methods that applied to it.

Consider a character in a video game(say Skyrim). That character you control as well as other non-playable ones in the game are internally represented by objects. When you strike a wild wolf in the game with your sword you are actually interacting with that character and affecting one of it's attributes, namely it's health.

So, you can say that an object is an entity which has a current state described by some attributes and can perform some methods.

Attributes : An attribute of an object is something that partly or wholly describes it's state.

Methods : Methods of an object are the actions which the object can perform.

Let us consider a living organism such as the simplest cell.

Attributes :

  1. Living (Boolean) : Can only be alive or dead.
  2. In Motion (Boolean) : Can either be moving or not.
  3. Speed (Float) : Can move at a specific speed at a given instant of time.
    .......

Methods :

  1. Ingest (void) : Ingest food that it finds.
  2. Move (void) : Moves at a certain velocity.
    .......

Objects have a pre-defined set of attributes and methods that they are characterised by. In other words, every object needs to follow a blueprint. This blueprint or set of guidelines that properly define the aforementioned characteristics is called a class.

So, we could all be objects of BITSian type, or an object of the more broader human type. You can also clearly make out that BITSian type comes under human type but we will discuss that later(something called inheritance).

Class

As defined earlier, a class is a blueprint for an object, something that prescribes the attributes and methods of an object of that certain class type. Let us see an example.


In [ ]:
LIMIT = 800000

class BITSian(object):    
    def __init__(self, name, id_no, parent_income):
        self.name = name
        self.id_no = id_no
        self.parent_income = parent_income
    
    def get_mcn(self, limit, falsify_tax_document=False):
        if falsify_tax_document:
            return True
        else:
            if self.parent_income <= limit:
                return True
            return False


a = BITSian("Arif Ahmed", "2015B4A70370G", 1045000)
print(a.get_mcn(LIMIT))

b = BITSian("Anonymous", "2015B*A*0***G", 2740000)
print(b.get_mcn(LIMIT, True))

So, let us analyze the various elements of the code :


class BITSian(object):

The above statement is a class definition, very much like function definition. This is the syntax :

class <classname>(<class type to inherit from>)

The default class type to inherit from is object type. Inheritance is something we will see in the next lecture.

Every object we define whether user-defined or not is of type object.


In [ ]:
class Lite(object):
    def __init__(self):
        pass

a = 5
b = "This is a String"
c = Lite()

print(isinstance(a, object))
print(isinstance(b, object))
print(isinstance(c, object))

def __init__(self, name, id_no, parent_income):
        self.name = name
        self.id_no = id_no
        self.parent_income = parent_income

Constructor

Written above is the constructor of the class. The constructor is a special function that enables the creation of an object given it's class type. It is mainly used to initialize the object with default initial values.

When we use something like self.<attribute> we are saying that <attribute> belongs to self. So, when creating an object say --> a = BITSian("Mr.XYZ", "2015XXXX0XXXG", 10**6), what happens internally is that the constructor is called and the name attribute of BITSian object a is set to string value Mr.XYZ. Similar for id_no and parent_income.

Class Attributes and Static Methods

Attributes defined at class level(that is common for all objects of that class type). That is a common value for all objects of that class type unless explicitly changed. When that explicit change is done, obviously the class attribute for that object will change NOT the general class attribute(this is pretty logical).

The explicit change can also be done for the class attribute itself, but this is bad software engineering practice. If you indeed need this to be done, you should change the initial value in the class body instead.

Go through below example for clarification.


In [ ]:
LIMIT = 800000

class BITSian(object): 
    
    # Class Attributes
    count = 0
    
    def __init__(self, name, id_no, parent_income):
        self.name = name
        self.id_no = id_no
        self.parent_income = parent_income
    
    def get_mcn(self, limit, falsify_tax_document=False):
        if falsify_tax_document:
            return True
        else:
            if self.parent_income <= limit:
                return True
            return False

    def change(self):
        # This is obviously not a realistic function :P
        self.count += 1

a = BITSian("ABC", "2012A4PS213G", 10**10)
b = BITSian("XYZ", "2012A1PS013G", 10**20)

print("Class level count : ", BITSian.count)
print("a count : ",a.count)
print("b count : ",b.count)
b.change()
print("b count : ",b.count)
print("Class level count : ",BITSian.count)
BITSian.count = 0
print("Class level count : ",BITSian.count)
print("b count : ",b.count)

The method equivalent of class attributes would be static methods. That is, methods which are defined at class level and do not accept self in it's function definition. These are mainly used when there a common similar functionality has to be implemented for all objects of the class type.


In [ ]:
class BITSian(object): 
    
    # Class Attributes
    human = True
    
    def __init__(self, name, id_no, parent_income):
        self.name = name
        self.id_no = id_no
        self.parent_income = parent_income
    
    def get_mcn(self, limit, falsify_tax_document=False):
        if falsify_tax_document:
            return True
        else:
            if self.parent_income <= limit:
                return True
            return False

    def change(self):
        # This is obviously not a realistic function :P
        self.human = False
        
    @staticmethod
    def is_IITian():
        return False

In [ ]:
a = BITSian("ABC", "2012A4PS213G", 10**10)
b = BITSian("XYZ", "2012A1PS013G", 10**20)

print(a.is_IITian())
print(b.is_IITian())
print(BITSian.is_IITian())

In the follwing lectures we will study about Inheritance, Polymorphism and Abstract Classes.