Object-Oriented Programming In Python(OOP)

Introduction

Object-Oriented Programming (OOP) is a method of structuring a program by clustering common properties and behavior into individual objects. OOP supports code reusability, abstraction, and encapsulation. OOP is based on the concept of objects that contains code and data. OOP languages are diverse, but the most popular is class-based, where objects are instances of the class and also determine their types.

Let’s take an example and understand the terms

class Student:
	College = "HCOE"
	
	def __init__(self, name, id, address):
		self.name = name
		self.id = id
		self.address = address
		self.dress_color = "white"
		
	def show(self):
		print("Name: ", self.name)
		print("Id: ", self.id)
		print("Address: ", self.address)
			
s1 = Student("Shiv", 34, "Rupandehi")

s2 = Student("Rajesh", 35, "Kapilvastu")

print("First student: \n")
s1.show()

print("\nSecond student: \n")
s2.show()

Output

First student:

Name:  Shiv
Id:  34
Address:  Rupandehi

Second student:

Name:  Rajesh
Id:  35
Address:  Kapilvastu
  • Class: Class is a user-defined prototype for an object that defines an attribute that characterizes the object of a class. Class cluster the data and methods. In the above example, “Student” is the name of a class.
  • Object: A unique instance of data structure that is defined by its class. The object comprises data and methods. In the above example, s1 is an object of class “Student” and is created using the name of the class.
  • Method: Methods are nothing but functions that are defined inside a class. In the above example __init__ and show() are two methods.
  • Class Variable: class variables are those variables that are defined inside a class but outside any of the methods of the class. In the above example, College is the class variable.
  • Instance variable: Instance variables are those variables that are defined inside any method. In the above example, name, id, address, and color are instance variables.
  • __init__(self): This is a special kind of method that is known as a constructor or initializer. Here, Python uses self-in methods to represent instances of that class. With this self keyword, an object can access the data and methods of the class.
  • Instance: Instance is the individual object of a class.

Let’s look at some of the built-in methods

class Student:
	def __init__(self,, name, id, address):
		self.name = name
		self.id = id
		self.address = address
		
	def __str__(self):
		return self.name + " lives in " +self.address + " and has id no. " + str(self.id)
		
	def __repr__(self):
		return [self.name, self.id, self.address]
		
s1 = Student("Shiv", 34, "Hong Kong")
s2 = Student("Ram", 45, "Jajarkot")

print(s1)
print(s2)
print("\n")

print(s1.__repr__())
print(s2.__repr__())

print("\n")
print(s1.__dict__)
print(s2.__dict__)

Output

Shiv lives in Hong Kong and has id no. 34
Ram lives in Jajarkot and has id no. 45


['Shiv', 34, 'Hong Kong']
['Ram', 45, 'Jajarkot']


{'name': 'Shiv', 'id': 34, 'address': 'Hong Kong'}
{'name': 'Ram', 'id': 45, 'address': 'Jajarkot'}
  • __str__(self): This method returns the output in string format.
  • __repr__(self): This method doesn’t return the output in string format. It returns in a tuple, list, and dictionary format.
  • __dict__ is used to get the elements of the class into dictionary format.

Let’s say we want to set items and get items inside a class. We can do this job using methods such as

class Student:
	def __init__(self):
		self.info= {}
		
	def setitems(self, key, value):
		self.info[key] = value
		
	def getitems(self, key):
		return self.info[key]
		
s1 = Student()

#setting items
s1.setitems("Name", "Rajesh")
s1.setitems("Address", "Kapilvastu")

#getting items
print(s1.getitems("Name"))
print(s1.getitems("Address"))

Output

Rajesh
Kapilvastu

We got what we wanted to do. But we’ve to call those two functions every time when we want to set and get items which can be a little bit tedious work to do. Python has __setitems__() and __getitems__() to perform the same task as above. Here’s how it can be done

class Student:
	def __init__(self):
		self.info= {}
		
	def __setitem__(self, key, value):
		self.info[key] = value
		
	def __getitem__(self, key):
		return self.info[key]
		
		
s1 = Student()

#setting items
s1["Name"] = "Rakesh"
s1["Id"] = 34
s1["Address"] = "Taplejung"

#getting items
print("Name : ", s1["Name"])
print("Id : ", s1["Id"])
print("Address : ", s1["Address"])

Output

Name :  Rakesh
Id :  34
Address :  Taplejung

In this way we can use __setitem__() and __getitem__().

 

Inheritance

Inheritance allows the user to transfer all the characteristics of the base class to the derived class. The object of derived class can also access the data and methods of its parent class

class Student:
	college = "HCOE"
	
	def __init__(self):
		print("Base class constructor.")

	def show(self):
		print("i'm in Base class.")
		
class Employee(Student):
	def __init__(self):
		print("Derived class constructor.")
		
	def display(self):
		print("i'm in derived class.")
	
#object of derived class - Employee
e1 = Employee()

#accessing class variable
print(e1.college)

#accessing Base class method from derived class
e1.show()

#accessing own method
e1.display()

Output

Derived class constructor.
HCOE
i'm in Base class.
i'm in derived class.

This is a simple example of inheritance, where we derived the Employee class from the Student class. When the object of the derived class is made, it calls the derived class constructor. Since the Employee class is derived from the Student class, the object of the Employee class can access the method of the Student class i.e show() method.

A child or derived class can have more than one parent class, which is known as multiple inheritances.

class Student:
	def __init__(self):
		print("Base class constructor - Student.")

	def show(self):
		print("i'm in Base class - Student.")
		
class Manager:
	def __init__(self):
		print("Base class constructor - Manager.")

	def flash(self):
		print("i'm in Base class - Manager.")
		
class Employee(Student, Manager):
	def __init__(self):
		print("Derived class constructor.")
		
	def display(self):
		print("i'm in derived class.")
	
#object of derived class - Employee
e1 = Employee()

#accessing Base class method from derived class
e1.show()


#accessing Base class method from derived class
e1.flash()

#accessing own method
e1.display()
print("\n")

#method resolution order for Derived class
print(Employee.mro())

Output

Derived class constructor.
i'm in Base class - Student.
i'm in Base class - Manager.
i'm in derived class.


[<class '__main__.Employee'>, <class '__main__.Student'>, <class '__main__.Manager'>, <class 'object'>]

This is how we can do multiple inheritances in python. Method Resolution Order (MRO) for derived class shows that first of all, python looks for the methods and data of the derived class and then for the method and data of parent classes. In the above example, MRO for Employee shows, first of all, the method of the derived class will be executed and then class Student and then Manager because it follows the order in which class is derived from the parent class.

Multiple classes can be derived from the single base class. Here’s how it can be done

class Student:
	def __init__(self):
		print("Base class constructor.")

	def show(self):
		print("i'm in Base class.")
		
class Manager(Student):
	def __init__(self):
		print("Derived class constructor - Manager.")

	def display(self):
		print("i'm in derived class - Manager.")
		
class Employee(Student):
	def __init__(self):
		print("Derived class constructor - Student.")
		
	def display(self):
		print("i'm in derived class - Student.")
	
#object of derived class
m1 = Manager()
e1 = Employee()
print("\n")

#accessing Base class method from derived class
e1.show()
m1.show()
print("\n")

#accessing own method
e1.display()
m1.display()
print("\n")

#displaying MRO for both derived class
print(Employee.mro())
print("\n")
print(Manager.mro())

Output

Derived class constructor - Manager.
Derived class constructor - Student.


i'm in Base class.
i'm in Base class.


i'm in derived class - Student.
i'm in derived class - Manager.


[<class '__main__.Employee'>, <class '__main__.Student'>, <class 'object'>]


[<class '__main__.Manager'>, <class '__main__.Student'>, <class 'object'>]

 

Method overloading

Two methods with the same name and same parameters is not allowed. We can modify the method such that it can show different reactions according to different numbers and types of parameters which are called method overloading.

class Student:
	def show(self,name = None, id = None):
		if name !=None and id == None:
			print("Name : ", name)
			
		elif name == None and id != None:
			print("Id : ", id)
			
		elif name is None and id is None:
			print("Nothing to show")
			
		else:
			print({"Name":name, "Id":id})


s1 = Student()
#passing nothing
s1.show()

#passing only name
s1.show(name = "Ram")

#passing only id
s1.show(id = 45)

#passing both name and id
s1.show("ram", 45)

Output

Nothing to show
Name :  Ram
Id :  45
{'Name': 'ram', 'Id': 45}

Here, class student has a method called show(). This method is overloaded so it can perform multiple functions based on its parameters. First of all, no parameters were passed so it shows nothing to show. While passing name and id at the same time it shows both the name and id. Also when the only name is passed and only the id is passed the same function was able to handle all the activities. So, this is the advantage of method overloading.

 

Method overriding

Method overriding is the ability of programming language that allows the method with the same name and same parameters in both parent and child class. If both parent and child class have same method with same parameters then the method of child class is said to override the method of parent class.

class Student:
	def show(self):
		print("This is base class - Student")
		
class Employee(Student):
	def show(self):
		print("This is derived class - Employee")
		

e1 = Employee()
e1.show()

Output

This is derived class - Employee

Here, the base class and derived class have the same method named as to show(). The derived class method overrides the method of base class.

 

Operator overloading

Operator overloading is the technique in which an operator is made to perform any task. + Operator in python performs integer addition and string concatenation, which is possible because of operator overloading.

class Student:
	def __init__(self, name, address):
		self.name = name
		self.address = address
		
	def show(self):
		print(self.name)
		print(self.address)
			
s1 = Student("Rajesh", "Kapilvastu")
s2 = Student("Shiv", "Rupandehi")

print(s1 < s2)

Output

Traceback (most recent call last):
  File "/data/user/0/ru.iiec.pydroid3/files/accomp_files/iiec_run/iiec_run.py", line 31, in <module>
    start(fakepyfile,mainpyfile)
  File "/data/user/0/ru.iiec.pydroid3/files/accomp_files/iiec_run/iiec_run.py", line 30, in start
    exec(open(mainpyfile).read(),  __main__.__dict__)
  File "<string>", line 13, in <module>
TypeError: '<' not supported between instances of 'Student' and 'Student'

Here, we got the type error as < operator cannot compare two instances of class directly. If we want to compare two instances of class we need to overload the operator.

class Student:
	def __init__(self, name, address):
		self.name = name
		self.address = address
	
	def __lt__(self, other):
		l1 = len(self.name) + len(self.address)
		l2 = len(other.name) + len(other.address)
		return l1 < l2
		
	def show(self):
		print(self.name)
		print(self.address)
			
s1 = Student("Rajesh", "Kapilvastu")
s2 = Student("Shiv", "Rupandehi")

print(s1 < s2)
print(s1 > s2)

Output

False
True

We overloaded the < operator so that it can compare two instances of the class. We use the __lt__() method which is a built-in method to overload the operator. Two parameter are passed in this method, one is ‘self’ and another is ‘other’. They are used to point to the individual instances of a class. When we execute s1 < s2, this statement is equivalent to s1.__lt__(s2). An instance of a class is passed as an argument.

 

Encapsulation

With the help of OOP, Python provides data encapsulation by which we can restrict some data and methods from being accessed. For this single underscore (_) or double underscore(__) is used as a prefix.

class Student:
	def __init__(self):
		self.__name = "rovman"
		self._address = "Nepaljung"
		
	def show(self):
		print(self.__name)
		print(self._address)
			
		
s1 = Student()
print("Before updating name and address: ")
s1.show()

print("\n")

#update values
s1.__name = "carlos"
s1._addrees = "Dang"
print("after updating name and address")
s1.show()

Output

Before updating name and address:
rovman
Nepaljung


after updating name and address
rovman
Nepaljung

We can got same output instead of updating those two instance variables because python treats them as private variables. If we want actually need to change them, we should use the setter function.

Let’s take another example of encapsulation

class Student:
	def __init__(self):
		self.__name = "rovman"
		self._address = "Nepaljung"
		
	def show(self):
		print(self.__name)
		print(self._address)
		
class Employee(Student):
	pass
			
		
e1 = Employee()
print(e1.__name)

Output

Traceback (most recent call last):
  File "/data/user/0/ru.iiec.pydroid3/files/accomp_files/iiec_run/iiec_run.py", line 31, in <module>
    start(fakepyfile,mainpyfile)
  File "/data/user/0/ru.iiec.pydroid3/files/accomp_files/iiec_run/iiec_run.py", line 30, in start
    exec(open(mainpyfile).read(),  __main__.__dict__)
  File "<string>", line 15, in <module>
AttributeError: 'Employee' object has no attribute '__name'

The Employee class is the child class of the Student class. By default all the method and instance variables must be inherited from the child class. But we got an attribute error that says the derived class doesn’t have the __name variable because it is a private variable of the base class so it hides that variable in the derived class.

 

Use of super() in OOP

super() returns the temporary object of super class that allows the user to access the method of the base class.

class Student:
	def __init__(self, name, address):
		self.name = name
		self.address = address
		
	def show(self):
		print(self.name)
		print(self.address)
		
class Employee(Student):
	def __init__(self):
		super().__init__("Rajesh", "Kapilvastu")
			
		
e1 = Employee()
e1.show()

Output

Rajesh
Kapilvastu

Here, super() is used to call the base class constructor to initialize the name and address variable. We assigned the values by calling super().__init__(self, name, address) instead of e1.__init__(self, name, address). Here the important thing is that we assigned the variables using base class constructor without using name of derived class. With this we can change the name of derived class at any time. Here’s how

class Student:
	def __init__(self, name, address):
		self.name = name
		self.address = address
		
	def show(self):
		print(self.name)
		print(self.address)
		
class E(Student):
	def __init__(self):
		#this remains same
		super().__init__("Rajesh", "Kapilvastu")
			
		
e1 = E()
e1.show()

Output

Rajesh
Kapilvastu

We change the name of the derived class while super() is still the same and still we got the correct output. This means we can change the name of the derived class at any time.

class Student:
	def __init__(self, name, address):
		self.name = name
		self.address = address
		
	def show(self):
		print(self.name)
		print(self.address)
		
class E(Student):
	def __init__(self):
		#this remains same
		super().__init__("Rajesh", "Kapilvastu")
			
	def display(self):
		super().show()
		
e1 = E()
e1.display()

Output

Rajesh
Kapilvastu

We can use super() to access the method of base class also.

 

Conclusion

OOP in Python provides data encapsulation, inheritance, and polymorphism which is achieved by operator overloading and method overloading. super() built-in function return the product object of base class. Using super(), we can access the data and methods of the base class in the derived class without using the name of a derived class. Classes allow us to bind the data and method together.

Happy Learning 🙂

References

https://www.tutorialspoint.com/python/

Leave a Comment