Python Lab 7 : Object Oriented Programming in Python

Object oriented Approach not only allows developers to reuse but with the help of polymorphism , inheritance and encapsulation it allows developers to easily read,manage,update, delete and fix bugs in the code.

Class in python

A class is a code ‘template’/’blueprint’ for creating objects on which functions are performed.It’s important to note that a class just provides structure—it’s a blueprint for how something should be defined, but it doesn’t actually provide any real content itself. 

class employee:
    pass

Object in Python

While the class is the blueprint, an instance is a copy of the class with actual values, literally an object belonging to a specific class.This example shows that two objects for class employee have been created which are unique and have unique locations in the memory, same is also verified emp1==emp2

class employee:
    pass
emp1 = employee()
emp2 = employee()
print(emp1)
print(emp2)
print(emp1==emp2)

Show Output

  <__main__.employee object at 0x00000278EB851160>
    <__main__.employee object at 0x00000278EB8510F0>
    False

Methods in Python

Methods are nothing but functions with classes. The only difference is methods automatically pass instance as its 1st argument which is called as self.

Initialization method or __init__() method in python

__init__ is a special method in Python classes, it is the constructor method for a class. In the following example you can see how to use it. __init__ is called when ever an object of the class is constructed.Basically __init__() is the first method which you will define after creating a class.All Instance variables or object variables are mentioned inside the __init__() method.

class employee:    
    def \_\_init\_\_(self,first,last,pay):
        self.first = first
        self.last = last
        self.pay = pay
        self.email = first + '.'+last+'@abc.com'
emp1 = employee('sameer','joshi',50000)
emp2 = employee('test','user',70000)    
print(emp1.first)
print(emp2.first)

Show Output

 sameer
test

User defined methods in Python

In the beow example we have created a method named fullname() this is called as user defined method.Note , self specifies that instance/object will be passed as first argument

class employee:
    def \_\_init\_\_(self,first,last,pay):
        self.first = first
        self.last = last
        self.pay = pay
        self.email = first + '.'+last+'@abc.com'
    def fullname(self):
        return '{} {}'.format(self.first,self.last)
emp1 = employee('sameer','joshi',50000)
emp2 = employee('ram','yadav',70000)     
print(emp2.fullname())
print(employee.fullname(emp1))

Show Output

    ram yadav
    sameer joshi

Class Variables in python

Instance variable has to be unique for each instance/object but class variable have to be the same.Let us explain this with an example which will be common to all the employees that is raise or salary hike.

class employee:
    raise\_amount=2    
    def \_\_init\_\_(self,first,last,pay):
        self.first = first
        self.last = last
        self.pay = pay
        self.email = first + '.'+last+'@abc.com'
    def fullname(self):
        return '{} {}'.format(self.first,self.last)
    def apply\_raise(self):
        self.pay = int(self.pay\*employee.raise\_amount)
        return self.pay
emp1 = employee('sameer','joshi',50000)
emp2 = employee('ram','yadav',70000)     
print(emp1.pay)
print(emp1.apply\_raise())

Show Output

  50000
  100000

Example to find out total number of employees

class employee:
    num\_of\_emp=0
    
    def \_\_init\_\_(self,first,last,pay):
        self.first = first
        self.last = last
        self.pay = pay
        self.email = first + '.'+last+'@abc.com'
        employee.num\_of\_emp+=1

emp1 = employee('sameer','joshi',50000)
emp2 = employee('ram','yadav',70000)
emp3=employee('test','user',12345)

print(employee.num\_of\_emp)

Show Output

  3

Class Methods and Static Methods

Unlike other methods class method and static method are not bound to any object. So you don’t need any object or instance to call these methods.

Class Method

Uptil now we have seen that the regular methods automatically takes instances as 1st arguments which is called self. Similarly class methods take class as 1st argument which by convention is called cls.To create a class method you first have to declare that you are using the below method as class method and same is done by using a decorator called @classmethod.Classmethods can also be called by objects. It is not necessary to call class variable using class name only

class employee:
    raise\_amount=2        
    def \_\_init\_\_(self,first,last,pay):
        self.first = first
        self.last = last
        self.pay = pay
        self.email = first + '.'+last+'@abc.com'        
    @classmethod
    def set\_raise\_amount(cls,amount):        
        cls.raise\_amount = amount   
     
emp1 = employee('sameer','joshi',50000)
emp2 = employee('ram','yadav',70000)
employee.set\_raise\_amount(4)

print(employee.raise\_amount)
print(emp1.raise\_amount)
print(emp2.raise\_amount)

Show Output

   4
   4
   4

Static method

We saw that regular methods pass instance automatically as first argument which is called self, class methods pass class automatically as first argument which is called cls but static methods don’t pass anything automatically. So static methods are similar to our functions but they are included within class and have some logical connection with the class. In order to tell python that given method is static method we need to declare it by using decorator @staticmethod. Let see one example

class employee:            
    def \_\_init\_\_(self,first,last,pay):
        self.first = first
        self.last = last
        self.pay = pay
        self.email = first + '.'+last+'@abc.com'        
    @staticmethod
    def is\_workday(date):
        if date.weekday() == 5 or date.weekday() == 6:  
            return False
        else:
            return True        

import datetime
date = datetime.date(2020,7,21)
print('Is it a working day:',employee.is\_workday(date))

Show Output

    True

Inheritance

This allows us to inherit attributes and methods from parent class.This is useful as we can use the functionality of parent class in a subclass and then overwrite or add another functionality to it without affecting the parent class in any way.

class employee:            
    def \_\_init\_\_(self,first,last,pay):
        self.first = first
        self.last = last
        self.pay = pay
        self.email = first + '.'+last+'@abc.com'        
          
class developer(employee):
    pass

dev1 = developer('Divya','Shah',30000)
dev2 = developer('Nakul','Yadav',20000)

print(dev1.pay)
print(dev1.email)

Show Output

    30000
    Divya.Shah@abc.com

In above example we have used all the attributes from parent class.Now lets us try to create some new attributes in child class.In below example e have created a new instance variable called prog_lang for developer class

class employee:            
    def \_\_init\_\_(self,first,last,pay):
        self.first = first
        self.last = last
        self.pay = pay
        self.email = first + '.'+last+'@abc.com'        
          
class developer(employee):
        def \_\_init\_\_(self,first,last,pay,prog\_lang):
            self.prog\_lang = prog\_lang

dev1 = developer('Divya','Shah',30000,'Python')
dev2 = developer('Nakul','Yadav',20000,'Ansible')

print(dev1.prog\_lang)
print(dev1.email)

Show Output

```
Python
Traceback (most recent call last):
  File "C:/Users/Pallavi/AppData/Local/Programs/Python/Python36/my programs/oop.py", line 17, in <module>
    print(dev1.email)
AttributeError: 'developer' object has no attribute 'email'
```

you can see that we got correct output when tried to print(dev1.prog_lang) but when we tried to print(dev1.email) we got an error.This is because now developer class has its own __init__() method so it will not go to the parent class for instance variable.This can be avoided by telling child class that it has to refer the parent class for instance variable like firt,last and email

class employee:            
    def \_\_init\_\_(self,first,last,pay):
        self.first = first
        self.last = last
        self.pay = pay
        self.email = first + '.'+last+'@abc.com'        
          
class developer(employee):
        def \_\_init\_\_(self,first,last,pay,prog\_lang):
            self.prog\_lang = prog\_lang
            super().\_\_init\_\_(first,last,pay)

dev1 = developer('Divya','Shah',30000,'Python')
dev2 = developer('Nakul','Yadav',20000,'Ansible')

print(dev1.prog\_lang)
print(dev1.email)

Show Output

 Python
 Divya.Shah@abc.com

Polymorphism

Polymorphism and Method Overriding. In literal sense, Polymorphism means the ability to take various forms. In PythonPolymorphism allows us to define methods in the child class with the same name as defined in their parent class.

class employee:            
    def \_\_init\_\_(self,first,last,pay):
        self.first = first
        self.last = last
        self.pay = pay
        self.email = first + '.'+last+'@abc.com'

    def fullname(self):
        return '{} {}'.format(self.first,self.last)
          
class developer(employee):
        def \_\_init\_\_(self,first,last,pay,prog\_lang):
            self.prog\_lang = prog\_lang
            super().\_\_init\_\_(first,last,pay)

        def fullname(self):
            return 'my fullname is {} {} '.format(self.first,self.last)

emp1 = employee('Divya','Shah',30000)
dev1 =  developer('Nakul','Yadav',20000,'Ansible')

print(emp1.fullname())
print(dev1.fullname())

Show Output

   Divya Shah
   my fullname is Nakul Yadav 

@property decorator in python OOP

Property decorators allows us to access methods as attributes.Means when object emp1 will access the method fullname he will not have to use parentheses. He can access it as attribute.

class employee:
       def \_\_init\_\_(self,first,last,pay):
        self.first = first
        self.last = last
        self.pay = pay        
    
    @property
    def fullname(self):
        return '{} {}'.format(self.first,self.last)

emp1 = employee('sam','roberts',50000)
print(emp1.fullname)

Show Output

    sam roberts

@setter in python oop

Now if we can access a method as an attribute then we should be able to provide a value to it as we do for any instance variable.This can be achieved using @setter.Same is shown in below code

class employee:   
    def \_\_init\_\_(self,first,last,pay):
        self.first = first
        self.last = last
        self.pay = pay       
    
    @property
    def fullname(self):
        return '{} {}'.format(self.first,self.last)

    @fullname.setter
    def fullname(self,name):
        first,last=name.split(' ')
        self.first=first
        self.last=last

emp1 = employee('sam','roberts',50000)
print(emp1.fullname)
emp1.fullname='Harry Potter'
print(emp1.fullname)

Show Output

    sam roberts
    Harry Potter

Example : Replicate bank account to print the current balance as well as the amount deposited or withdrawn

class BankAccount:
    def \_\_init\_\_(self,name):
        
        self.name=name
        self.balance=0

    def withdraw(self, amount):
        print(self.name,'withdrawed:',amount)       
        self.balance -= amount
        print(self.name,'account balance is:',self.balance)        

    def deposit(self, amount):
        print(self.name,'Deposited:',amount)
        self.balance += amount
        print(self.name,'account balance is:',self.balance)       

accountholder1=BankAccount('John')
accountholder2=BankAccount('Peter')

accountholder1.deposit(100)
accountholder2.deposit(200)
accountholder1.withdraw(50)
accountholder2.withdraw(100)

Show Output

  John Deposited: 100
    John account balance is: 100
    Peter Deposited: 200
    Peter account balance is: 200
    John withdrawed: 50
    John account balance is: 50
    Peter withdrawed: 100
    Peter account balance is: 100