Python OOPS
atchukolanaresh
Posted on May 11, 2023
Python OOPS:
in Python, we have classes to help us bring all the related objects and functionality together so that it helps us to manage our code easily.
Unlike other programming languages, Python revolves around the concept of objects. Hence, it is an Object-Oriented Programming Language(OOPs).
Classes and objects are two main aspects of OOPs.
Classes are the user-defined blueprints that help us create an object. Objects are the instances of a particular class. Every other element in Python will be an object of some class, such as the string, dictionary, number(20,30), etc. will be an object of some corresponding built-in class(int, str) in Python.
Let’s take an example to understand this concept better.
Here, we have the blueprint of a car on the left side. This is known as the class while on the right side we have some of the models of the cars based on that blueprint, which are known as the objects of the particular class.
Refer to the following image below:
Creating a Class in Python
Creating a class is as easy as creating a function in Python. In function, we start with the def keyword while class definitions begin with the keyword class.
Following the keyword class, we have the class identifier(i.e. the name of the class we created) and then the : (colon) operator after the class name.
In the next indented lines(statement 1..n) are the members of the class. Also, the variables inside the class are known as attributes. The attributes can be accessed later on in the program using the dot(.) operator.
The syntax for creating a Class in Python is as follows:
class ClassName:
# Statement 1
# Statement 2
.
.
# Statement n
Let’s take a simple example of a class named Scaler.
class Scaler:
pass
Note: The keyword pass denotes an empty class. It is written just to avoid any errors in the console while running the above code.
Python Class Attributes and Methods
To make the most of Python Classes, we also need to add some functionality to the classes. We can make this possible with the help of attributes and methods.
We need to set some attributes which would contain some functions and data. These would add some functionality to our code.
Those functions which we define inside the class are known as methods.
Let’s take a look at these additional functionalities in brief.
Python Class Attributes
These are the variables that are defined inside the particular class and can be used by all the objects. These attributes can, later on, be called by using the class and attribute name with the dot(.) operator.
Let’s take a look at an example for further understanding.
class Scaler:
Course = 'Python'
Here we have a class named Scaler, with a class attribute named Course with a fixed value (ie ‘Python’).
Note: The value of any attributes will remain the same for all objects until and unless it is changed explicitly later on in the code.
Let’s continue with the above example to define some attributes and access it.
class Scaler:
Course1 = 'Python'
Course2 = 'C++'
Course3 = 'Java'
# Accessing the values of the attributes
print(Scaler.Course1)
print(Scaler.Course3)
# Accessing through object instantiation.
obj= Scaler()
print(obj.Course2)
Output:
Python
Java
C++
Here we have defined several attributes inside the Scaler Class. Also, we have shown two ways of accessing the values of those attributes. One, by directly using the class name and the other by using an object(class instance). Assigning a class to a variable is known as object instantiation.
Note: If we change the value of the attribute using the class name(the first method in the above example), then it would change across all the instances of that class. While if we change the value of an attribute using class instance(object instantiation), it would only change the value of the attribute in that instance only.
Let’s show an example to have clarity on this concept:
class Scaler:
Course = 'Python'
# Changing value using Class Name
Scaler.Course = 'Machine Learning'
obj= Scaler()
print(obj.Course)
# Changing value using Class Instance
obj.Course = 'AI'
print(obj.Course) # Value will change in this instance only
print('Using class instance would not reflect the changes to other instances')
print(Scaler.Course) # Value haven't changed
obj2= Scaler()
print(obj2.Course) # Value haven't changed
Output:
Machine Learning
AI
Using class instance would not reflect the changes to other instances
Machine Learning
Machine Learning
Python Class Methods
Once we have defined the class attributes, we can even define some functions inside the particular class that can access the class attribute and can add more functionality to the existing code.
These defined functions inside a class are known as methods. We can define as many methods as we want inside a single class.
Syntax:
We define a method(function) inside a class using the def keyword, followed by the method name. We then pass some arguments through the method.
Note: When we define a method, it must at least pass a single parameter which is generally named as self. It refers to the current instance of the class.
We can call this parameter by any name, other than self if we want.
Let’s take a look at an example of the same.
class Scaler:
def CourseDetails(self):
print('Course Information')
We can also pass other arguments inside the CourseDetails function if we want.
For example, let’s say we have a class Scaler, in which we have an attribute named “name” and a method named CourseDetails. The method would take the argument name along with self. We can also pass multiple arguments as per our requirements in the code.
class Scaler:
name = 'Python'
def CourseDetails(self, name):
self.name = name
Instance Attributes (_init_method) in Python:
We can also provide the values of the attributes during the time of object creation. Instance attributes are attached to the instance of a class. Whenever a new object of the class is created, this method is called.
This is done by defining the attributes under the _init_method. The init method is similar to the constructors as in other programming languages(C++, Java). Such a method contains statements that would be executed during the run time(time of object creation).
Let’s take an example to have more clarity on this particular method. This method begins with a double underscore(__).
class Scaler:
# init method
def __init__(self, name):
self.name = name
# normal method
def CourseDetails(self):
print('The name of this course is', self.name)
study1 = Scaler('Python')
study1.CourseDetails()
study2 = Scaler('Machine Learning')
study2.CourseDetails()
Output:
The name of this course is Python
The name of this course is Machine Learning
Note: We can define separate attribute values for different objects(study1, study2) of a class.
Python Class Properties
In Python, with the property() function inside the class, we can define some additional functionalities.
The property() function, just like Java and other languages, takes the set, get and delete methods as arguments and returns an object. These functionalities can be easily incorporated into the code with property() function.
Let’s look at an example to understand the property() function –
class Scaler:
def __init__(self):
self.__name='Python'
def setCourseName(self, name):
print('"setCourseName()" is called here')
self.__name=name
def getCourseName(self):
print('"getCourseName()" is called here')
return self.__name
name=property(getCourseName, setCourseName)
obj = Scaler()
obj.name = "C++"
print(obj.name)
Output:
"setCourseName()" is called here
"getCourseName()" is called here
C++
Introduction
Python being an “object-oriented programming language”, has the concept of classes and objects to keep related things together.
Let's take the example of a bookshelf. Here we sort the books into different shelves according to their genre. The shelves are the classes, while the different books are the “objects”. Do refer to the article Classes in Python, before going through this blog for better understanding. We can also relate our programming with real-world objects through objects in python. We will discuss such scenarios with some examples later in this article.
Unlike other programming languages, Python revolves around the concept of OOPs. This way, we can keep related objects and functionality together so that it helps us to manage our code in an efficient manner. Classes and objects are two main aspects of OOPs.
Classes are the user-defined blueprints that help us create an “object”. Objects are the instances of a particular class. Every other element in Python will be an object of some class, such as the string, dictionary, number(10,40), etc. will be an object of some corresponding built-in class(int, str) in Python. Objects are different copies of the class with some actual values.
Let's take an example to understand this concept better. Here, we have the blueprint of a car on the left side. This is known as the class, while on the right side, we have some of the models of the cars based on that blueprint, which is known as the objects(instances of the class) of the particular class. Here we would be learning about objects in detail.
Refer to the following image below:
Creating an object in Python
After declaring a class in Python, when we create an instance of that class, it is known as the object of the class. The process of creating an object is called instantiation. The classes are essentially like a template to create the objects. The object is created using the name of the class. The object shares all the behavior and attributes of the class, such as the variables and values present in the class. Also, the object inherits the functions mentioned in the class, along with the class's behavior.
Before creating an object, we should know that it contains such properties to distinguish it from others.
- State - The state of an object is determined by the attributes of the object(i.e., the different items we have in the class, which the object inherits.).
- Behavior - The behavior is represented by the methods of the object. It shows the difference and similarities of the functionality of an object to other objects.
- Identity - Each and every object must be unique on its own. We can make this by giving it a unique name(like obj1, obj3, new_obj, etc.).
The syntax of creating an object of a particular class is as follows:
[object_name] = [class_name](arguments/parameters)
Here in comparison to the above example, the object_name is like the car models, namely, Hyundai, Ford, etc. The class_name is similar to the car blueprint. The arguments are just like the car's features, which can pass to some particular car models(objects).
Accessing Object's variables in Python
Let's take an example. Here we have created an object of the class named Scaler.
# Creating a class named Scaler
class Scaler:
a = 10
b = 25
c = 50
# Creating an object named "obj1", "obj2" & accessing the class properties(attributes).
obj1 = Scaler()
print(obj1.a)
print(obj1.b)
obj2 = Scaler()
print(obj2.c)
Output:
10
25
50
Accessing Object's functions in Python
As we know, inside classes, we can declare functions, which are known as methods. Objects can also contain such methods(functions) and pass the arguments through those methods. Methods of an object are the corresponding function of that particular class.
Let's take an example to understand this concept. We will create a method in the Scaler class and execute it on the object.
# Creating a class named Scaler
class Scaler:
def __init__(self, name, age, hobby):
self.name = name
self.age = age
self.hobby= hobby
def new(self):
print("Heyy my name is " + self.name + ". I Love " + self.hobby)
obj2 = Scaler("Vikram", 24, "coding")
obj2.new()
Output
Heyy my name is Vikram. I Love coding
In the above code snippet, we have created a class named Scaler. In it, we have defined two methods(init & new). We have created an object “obj2”, which is the instance of the class, and it accesses the function new(), while also passing three arguments. This way, the object can access the functions.
Note:.
a. The init method is called each and every time a new object is being created from any class. The init method is similar to the constructors as in other programming languages(C++, Java). Such a method contains statements that would be executed during the run time(time of object creation). This method begins with a double underscore(__).
b. Also, the first argument which is passed in the function of the class must be the object itself. So it’s conventionally called “self”, passed as the first argument, followed by the subsequent arguments (such as age, hobby, etc.). The “self” refers to the current instance of the class. We can call this parameter by any name other than “self” if we want.
Modifying Object's properties in Python
Once we declare objects, we can later modify their properties and values. Let's take an example to understand how this works. Here we would be creating a class named Scaler and would declare an object. We would be trying to change the object property by changing the attribute value.
# Creating a class named Scaler
class Scaler:
a = 10
# Declaring an object
obj1 = Scaler()
print(obj1.a)
#Modifying value
obj1.a = 200
print("After modifying the object properties")
print(obj1.a)
Output
10
After modifying the object properties
200
Here in the above code snippet, we set the value of the object “obj1”, from 10 to 200. By accessing the value of the attribute(here, it is “a”) through the object (i.e., obj1.a) and assigning a different value to it, we can change any of the properties of each and every object of a class.
Deleting Object's properties in Python
We can even delete the properties(also known as attributes) of an object with the help of the del keyword. Continuing with the previous example of the Scaler class, here we delete the “a” property from the “obj1” object.
del obj1.a
Deleting an Object in Python
You created an object earlier and now want to delete it? We also have a way to delete an object which was created earlier in the code by using the del keyword.
del obj1
Note: After deleting the object of a class, the name of the object which is bound to it gets deleted, but however the object continues to exist in the memory with no name assigned to it. Later it is automatically destroyed by a process known as garbage collection.
What is Inheritance in Python?
Inheritance is the ability to ‘inherit’ features or attributes from already written classes into newer classes we make. These features and attributes are defined data structures and the functions we can perform with them, a.k.a. Methods. It promotes code reusability, which is considered one of the best industrial coding practices as it makes the codebase modular.
In Python inheritance, new class/es inherits from older class/es. The new class/es copies all the older class's functions and attributes without rewriting the syntax in the new class/es. These new classes are called derived classes, and old ones are called base classes.
For example, inheritance is often used in biology to symbolize the transfer of genetic traits from parents to their children. Similarly, we have parent classes (Base classes) and child classes (derived classes). In Inheritance, we derive classes from other already existing classes. The existing classes are the parent/base classes from which the attributes and methods are inherited in the child classes.
Syntax:
class DerivedClass(BaseClass):
# Class definition
Here, DerivedClass is the class that will inherit from the BaseClass. The BaseClass is the class that will serve as the parent or superclass.
Example:
class Person:
def __init__(self, name, age): # Constructor to initialize name and age attributes
self.name = name
self.age = age
def say_hello(self): # Method to greet and introduce the person
print(f"Hello, my name is {self.name} and I am {self.age} years old.")
class Student(Person): # Student class inherits from Person class
def __init__(self, name, age, grade): # Constructor to initialize name, age, and grade attributes
super().__init__(name, age) # Call the parent class constructor to initialize name and age
self.grade = grade # Additional attribute specific to the Student class
def say_hello(self): # Override the say_hello method of the parent class
super().say_hello() # Call the parent class say_hello method to introduce the student as a person
print(f"I am a student in grade {self.grade}.") # Print additional information specific to the Student class
# Creating an instance of the base class
person = Person("John", 30)
person.say_hello()
# Creating an instance of the derived class
student = Student("Mary", 18, 12)
student.say_hello()
Output:
Hello, my name is John and I am 30 years old.
Hello, my name is Mary and I am 18 years old.
I am a student in grade 12.
In the above example, we define a base class called Person with an init method to initialize the name and age attributes, and a say_hello method to introduce the person. We also define a derived class called Student, which inherits from the Person class and adds a new attribute grade and a new implementation of the say_hello method to introduce the student.
We created an instance of the Person class and call its say_hello method, which outputs a message introducing the person. We also created an instance of the Student class and call its say_hello method, which outputs a message introducing the student and their grade.
Types of Inheritance in Python
Now that we are all set with the prerequisites to understand how inheritance in Python is carried out, let’s look at various inheritance types.
Single Inheritance in Python
Single Inheritance is the simplest form of inheritance where a single child class is derived from a single parent class. Due to its candid nature, it is also known as Simple Inheritance.
Example
# python 3 syntax
# single inheritance example
class parent: # parent class
def func1(self):
print("Hello Parent")
class child(parent):
# child class
def func2(self): # we include the parent class
print("Hello Child") # as an argument in the child
# class
# Driver Code
test = child() # object created
test.func1() # parent method called via child object
test.func2() # child method called
Output:
> Hello Parent
> Hello Child
Multiple Inheritance in Python
In multiple inheritance, a single child class is inherited from two or more parent classes. It means the child class has access to all the parent classes' methods and attributes.
However, if two parents have the same “named” methods, the child class performs the method of the first parent in order of reference. To better understand which class’s methods shall be executed first, we can use the Method Resolution Order function (mro). It tells the order in which the child class is interpreted to visit the other classes.
Example:
Basic implementation of multiple inheritance
# python 3 syntax
# multiple inheritance example
class parent1: # first parent class
def func1(self):
print("Hello Parent1")
class parent2: # second parent class
def func2(self):
print("Hello Parent2")
class parent3: # third parent class
def func2(self): # the function name is same as parent2
print("Hello Parent3")
class child(parent1, parent2, parent3): # child class
def func3(self): # we include the parent classes
print("Hello Child") # as an argument comma separated
# Driver Code
test = child() # object created
test.func1() # parent1 method called via child
test.func2() # parent2 method called via child instead of parent3
test.func3() # child method called
# to find the order of classes visited by the child class, we use __mro__ on the child class
print(child.__mro__)
Output:
> Hello Parent1
> Hello Parent2
> Hello Child
>(<class '__main__.child'>, <class '__main__.parent1'>, <class '__main__.parent2'>, <class '__main__.parent3'>, <class 'object'>)
As we can see with the help of mro, the child class first visits itself, then the first parent class, referenced before the second parent class. Similarly, it visits the second parent class before the third parent class, and that’s why it performs the second parent’s function rather than the third parent’s. Finally, it visits any objects that may have been created.
Multilevel Inheritance in Python
In multilevel inheritance, we go beyond just a parent-child relation. We introduce grandchildren, great-grandchildren, grandparents, etc. We have seen only two levels of inheritance with a superior parent class/es and a derived class/es, but here we can have multiple levels where the parent class/es itself is derived from another class/es.
Example:
class grandparent: # first level
def func1(self):
print("Hello Grandparent")
class parent(grandparent): # second level
def func2(self):
print("Hello Parent")
class child(parent): # third level
def func3(self):
print("Hello Child")
# Driver Code
test = child() # object created
test.func1() # 3rd level calls 1st level
test.func2() # 3rd level calls 2nd level
test.func3() # 3rd level calls 3rd level
Output:
> Hello Grandparent
> Hello Parent
> Hello Child
Hierarchical Inheritance in Python
Hierarchical Inheritance is the right opposite of multiple inheritance. It means that there are multiple derived child classes from a single-parent class.
Example:
# python 3 syntax
# hierarchical inheritance example
class parent: # parent class
def func1(self):
print("Hello Parent")
class child1(parent): # first child class
def func2(self):
print("Hello Child1")
class child2(parent): # second child class
def func3(self):
print("Hello Child2")
# Driver Code
test1 = child1() # objects created
test2 = child2()
test1.func1() # child1 calling parent method
test1.func2() # child1 calling its own method
test2.func1() # child2 calling parent method
test2.func3() # child2 calling its own method
Output:
> Hello Parent
> Hello Child1
> Hello Parent
> Hello Child2
Hybrid Inheritance in Python
Hybrid Inheritance is the mixture of two or more different types of inheritance. Here we can have many relationships between parent and child classes with multiple levels.
Example:
# python 3 syntax
# hybrid inheritance example
class parent1: # first parent class
def func1(self):
print("Hello Parent")
class parent2: # second parent class
def func2(self):
print("Hello Parent")
class child1(parent1): # first child class
def func3(self):
print("Hello Child1")
class child2(child1, parent2): # second child class
def func4(self):
print("Hello Child2")
# Driver Code
test1 = child1() # object created
test2 = child2()
test1.func1() # child1 calling parent1 method
test1.func3() # child1 calling its own method
test2.func1() # child2 calling parent1 method
test2.func2() # child2 calling parent2 method
test2.func3() # child2 calling child1 method
test2.func4() # child2 calling its own method
Output:
> Hello Parent1
> Hello Child1
> Hello Parent1
> Hello Parent2
> Hello Child1
> Hello Child2
This example shows a combination of three types of python inheritance.
Parent1 -> Child1 : Single Inheritance
Parent1 -> Child1 -> Child2 : Multi – Level Inheritance
Parent1 -> Child2 <- Parent2 : Multiple Inheritance
The diagrammatic explanation of this hybrid inheritance is:
Special Functions in Python Inheritance
Python is a very versatile and user-friendly language. It provides some amazing in-built functions that make our lives easier when understanding inheritance, especially of a complex nature.
super() Function
Method overriding is an ability of any object-oriented programming language that allows a subclass or child class to provide a specific implementation of a method already provided by one of its super-classes or parent classes. This discrepancy is caused due to similar naming convention of the methods. Commonly we can see this situation when the parent’s init() is overridden by the child’s init(), and hence the child class cannot inherit attributes from the parent class.
Similarly, we can face this same problem with methods other than init but having the same naming convention across parent and child classes.
One solution is to call the parent method inside the child method.
# python 3 syntax
# solution to method overriding - 1
class parent: # parent class
def __init__(self): # __init__() of parent
self.attr1 = 50
self.attr2 = 66
class child(parent): # child class
def __init__(self): # __init__() of child
parent.__init__(self) # calling parent’s __init__()
self.attr3 = 45
test = child() # object initiated
print (test.attr1) # parent attribute called via child
Output:
> 50
Another way to solve this problem without explicitly typing out the parent name is to use super(). It automatically references the parent/base class from which the child class is derived. It is extremely helpful to call overridden methods in classes with many methods.
Example:
# python 3 syntax
# solution to method overriding - 2
class parent: # parent class
def display(self): # display() of parent
print("Hello Parent")
class child(parent): # child class
def display(self): # display() of child
super().display() # referencing parent via super()
print("Hello Child")
test = child() # object initiated
test.display() # display of both activated
Output:
> Hello Parent
> Hello Child
Alternately we can also call super() with the following syntax:
super(child,self).display()
Here, the first parameter references the child class/subclass in the function.
issubclass()
The issubclass() function is a convenient way to check whether a class is the child of the parent class. In other words, it checks if the first class is derived from the second class. If the classes share a parent-child relationship, it returns a boolean value of True. Otherwise, False.
isinstance()
isinstance() is another inbuilt function of Python that allows us to check whether an object is an instance of a particular class or any of the classes it has been derived from. It takes two parameters, i.e. the object and the class we need to check it against. It returns a boolean value of True if the object is an instance and otherwise, False.
Example:
# python 3 syntax
# issubclass() and isinstance() example
class parent: # parent class
def func1():
print("Hello Parent")
class child(parent): # child class
def func2():
print("Hello Child")
# Driver Code
print(issubclass(child,parent)) # checks if child is subclass of parent
print(issubclass(parent,child)) # checks if parent is subclass of child
A = child() # objects initialized
B = parent()
print(isinstance(A,child)) # checks if A is instance of child
print(isinstance(A,parent)) # checks if A is instance of parent
print(isinstance(B,child)) # checks if B is instance of child
print(isinstance(B,parent)) # checks if B is instance of parent
Output:
True
False
True
True
False
True
Advantages of Inheritance in Python
- Modular Codebase: Increases modularity, i.e. breaking down the codebase into modules, making it easier to understand. Here, each class we define becomes a separate module that can be inherited separately by one or many classes.
- Code Reusability: the child class copies all the attributes and methods of the parent class into its class and use. It saves time and coding effort by not rewriting them, thus following modularity paradigms.
-
Less Development and Maintenance Costs: Changes need to be made in the base class, all derived classes will automatically follow.
Disadvantages of Inheritance in Python
- Decreases the Execution Speed: loading multiple classes because they are interdependent
- Tightly Coupled Classes: this means that even though parent classes can be executed independently, child classes cannot be executed without defining their parent classes.
What is Encapsulation in Python?
Encapsulation is one of the cornerstone concepts of OOP. The basic idea of Encapsulation is to wrap up both data and methods into one single unit. The way that data and methods are organized does not matter to the end-user. The user is only concerned about the right way to provide input and expects a correct output on the basis of the inputs provided.
Encapsulation in Python also ensures that objects are self-sufficient functioning pieces and can work independently.
Why do we need Encapsulation in Python?
The advantages of Encapsulation in Python can be summed up as follows –
1. Encapsulation provides well-defined, readable code
The primary advantage of using Encapsulation in Python is that as a user, we do not need to know the architecture of the methods and the data and can just focus on making use of these functional, encapsulated units for our applications. This results in a more organized and clean code. The user experience also improves greatly and makes it easier to understand applications as a whole.
Prevents Accidental Modification or Deletion
Another advantage of encapsulation is that it prevents the accidental modification of the data and methods. Let’s consider the example of NumPy again, if I had access to edit the library, then I might make a mistake in the implementation of the mean function and then because of that mistake, thousands of projects using NumPy would become inaccurate.
3. Encapsulation provides security
Encapsulation in Python is achieved through the access modifiers. These access modifiers ensure that access conditions are not breached and thus provide a great user experience in terms of security.
Access Modifiers in Python encapsulation
Sometimes there might be a need to restrict or limit access to certain variables or functions while programming. That is where access modifiers come into the picture.
Now when we are talking about access, 3 kinds of access specifiers can be used while performing Encapsulation in Python. They are as follows :
- Public Members
- Private Members
- Protected Members
Encapsulation in Python using public members
As the name suggests, the public modifier allows variables and functions to be accessible from anywhere within the class and from any part of the program. All member variables have the access modifier as public by default.
Now let’s check out how we can implement Encapsulation in Python using public methods –
# illustrating public members & public access modifier
class pub_mod:
# constructor
def __init__(self, name, age):
self.name = name;
self.age = age;
def Age(self):
# accessing public data member
print("Age: ", self.age)
# creating object
obj = pub_mod("Jason", 35);
# accessing public data member
print("Name: ", obj.name)
# calling public member function of the class
obj.Age()
Evidently, from the above code, you can make out that we declared two variables and two methods of the class pub_mod. We were able to access the variables and methods wherever we wanted with ease as the access modifier for them was public, which means they should be accessible everywhere.
This claim is satisfied as we can see in the output –
Name: Jason
Age: 35
Encapsulation in Python using private members
The private access modifier allows member methods and variables to be accessed only within the class. To specify a private access modifier for a member, we make use of the double underscore __.
Let’s check out this example to understand how we can implement Encapsulation using private members –
# illustrating private members & private access modifier
class Rectangle:
__length = 0 #private variable
__breadth = 0#private variable
def __init__(self):
#constructor
self.__length = 5
self.__breadth = 3
#printing values of the private variable within the class
print(self.__length)
print(self.__breadth)
rect = Rectangle() #object created
#printing values of the private variable outside the class
print(rect.length)
print(rect.breadth)
We have accessed len both within and outside the class in the above snippet. Let’s see what kind of output that gives us.
Output –
5
3
Traceback (most recent call last) :
File "main.py", line 14, in <module>
print(rect.length)
AttributeError: 'Rectangle' object has no attribute 'length'
We can see that we have received an AttributeError in the output. Can you guess why?
Well, your thoughts should wander towards the private access modifier!
Since len is a private member and we have tried to access it outside the class, that is why we received the above error. We can access private members from outside of a class using the following two approaches
- Public method to access private members
- Name Mangling to access private members
Public method to access private members
# illustrating protected members & protected access modifier
class details:
_name="Jason"
_age=35
_job="Developer"
class pro_mod(details):
def __init__(self):
print(self._name)
print(self._age)
print(self._job)
# creating object of the class
obj = pro_mod()
Name Mangling to access private members
We can directly access private and protected variables from outside of a class through name mangling. The name mangling is created on an identifier by adding two leading underscores and one trailing underscore, like this classname_dataMember, where classname is the current class, and data member is the private variable name.
class details:
_name="Jason"
_age=35
_job="Developer"
class pro_mod(details):
def __init__(self):
print(self._name)
print(self._age)
print(self._job)
# creating object of the class
obj = pro_mod()
# direct access of protected member
print("Name:",obj._name)
print("Age:",obj._age)
Encapsulation in Python using protected members
What sets protected members apart from private members is that they allow the members to be accessed within the class and allow them to be accessed by the sub-classes involved. In Python, we demonstrate a protected member by prefixing with an underscore _ before its name.
As we know, if the members have a protected access specifier, it can also be referenced then within the class and the subsequent sub-clas
So now let’s see this concept in action –
# illustrating protected members & protected access modifier
class details:
_name="Jason"
_age=35
_job="Developer"
class pro_mod(details):
def __init__(self):
print(self._name)
print(self._age)
print(self._job)
# creating object of the class
obj = pro_mod()
# direct access of protected member
print("Name:",obj.name)
print("Age:",obj.age)
Output –
Jason
35
Developer
Traceback (most recent call last):
File "main.py", line 15, in <module>
print("Name:",obj.name)
AttributeError: 'pro_mod' object has no attribute 'name'
It is quite clear from the output that the class pro_mod was successfully able to inherit the variables from the class details and print them to the console, although they were protected variables. And when we tried to refer to them outside of their parent class and the sub-class, we got an AttributeError for the same.
Advantages of Encapsulation
Code reusability: Encapsulation in Python allows developers to create reusable code by hiding the implementation details of an object or class and only exposing a public interface for interacting with it.
Data hiding: Encapsulation helps to protect the internal state of an object or class from being accessed or modified by external code, which improves the security of the application.
Improved maintainability: By encapsulating the implementation details of an object or class, developers can make changes to the internal state or behavior of the object without affecting external code that uses it.
Easier to understand: Encapsulation makes the code more organized and easier to understand by clearly separating the public interface from the implementation details.
Better control over class properties: Encapsulation allows developers to have better control over the properties and methods of a class, by making some properties private or protected and only allowing them to be accessed by specific methods.
-
Better class design: Encapsulation encourages developers to design classes that are cohesive and have a single responsibility, which makes the code more modular and easier to maintain.
What is Polymorphism in Python?
The literal meaning of Polymorphism is - having different forms. In programming, Polymorphism refers to a function having the same name but being used in different ways and different scenarios. This makes programming easier and more intuitive.
Polymorphism is one of the fundamental cornerstones of Object Oriented Programming. It is the ability of one function to display multiple functionalities all together. It basically creates a structure that can use many forms of objects.
Polymorphism in Python involves a child class inheriting all the methods from the parent class. This can be implemented in different ways which we will cover in detail in this article.
Polymorphism in Python Example
Let’s try to understand polymorphism and its implementation even better through this simple example -
print(10+256)
print("Scaler"+"Academy")
The above code snippet is the simplest example. Over here, the “+” operator is being overloaded.
You must be wondering what is meant by being “overloaded”. Let me shed some light on that-
Operator overloading is the concept of using an operator beyond its pre-defined purpose.
So in our example, the “+” operator is performing addition in the first line while the second line is an example of string concatenation.
The output would make the difference clear
Here, you can see how the same operator was used in two different ways. Hence, operator overloading can be said to be an implementation of Polymorphism in Python.
266
ScalerAcademy
The best example of Run-time polymorphism is method
Function of Polymorphism in Python
In Python, there are several functions that depict polymorphism by being compatible with multiple data types. The best example of this is the in-built len() function.
Let’s check out a small code snippet to see it in action -
print(len("Scaler Academy"))
print(len(["Python", "PHP", "C++"]))
print(len({"Name": "Rahul", "Address": "Kolkata,India"}))
Output-
14
3
2
From the above code snippet, we can clearly understand the len() function worked for all three data types- strings, lists and dictionaries.
Class Polymorphism in Python
Polymorphism in Python can also be implemented through classes. Python allows classes to have different methods having the same name.
An example would make this concept a lot clearer -
class Cat:
def __init__(self, name, age):
self.name = name
self.age = age
def info(self):
print(f"I am a cat. My name is {self.name}. I am {self.age} years old.")
def make_sound(self):
print("Meow")
class Cow:
def __init__(self, name, age):
self.name = name
self.age = age
def info(self):
print(f"I am a Cow. My name is {self.name}. I am {self.age} years old.")
def make_sound(self):
print("Moo")
cat1 = Cat("Kitty", 2.5)
cow1 = Cow("Fluffy", 4)
for animal in (cat1, cow1):
animal.make_sound()
animal.info()
animal.make_sound()
Now in this example, we have created two classes “Cat” and “Cow”. They’re different animals and make different sounds. So, the make_sound() function should produce two different outputs based on the objects we pass through them. In this case, we have created two objects “cat1” and “cow1”.
Now let’s see the final output that we will get -
Meow
I am a cat. My name is Kitty. I am 2.5 years old.
Meow
Moo
I am a Cow. My name is Fluffy. I am 4 years old.
Moo
As discussed, the make-sound() function is indeed producing two different outputs- “Meow” and “Moo”.
Polymorphism in Python with Inheritance
Polymorphism in Python lets us define the child class to have the same name methods as the parent class. On the other hand, inheritance in Python, the child class inherits all methods of the parent class.
Now, we may need to modify or re-define some of these. The process of making these changes specifically to the child class to ensure that they work seamlessly is known as Method Overriding.
Let’s look at a code snippet that implements Polymorphism with Inheritance in Python -
from math import pi
class Shape:
def __init__(self, name):
self.name = name
def area(self):
pass
def fact(self):
return "I am a two-dimensional shape."
def __str__(self):
return self.name
class Circle(Shape):
def __init__(self, radius):
super().__init__("Circle")
self.radius = radius
def area(self):
return pi*self.radius**2
shape_circle = Circle(7)
print(shape_circle)
print(shape_circle.area())
print(shape_circle.fact())
Note- You might have noticed that “pass” has been used within one of the functions.The pass statement in Python is used when you need it syntactically but do not wish for any code to be executed. It basically acts as a placeholder and prevents errors even when no code is written.
Output -
I am sure you can make out from the output that the class “Circle” did not have a fact() method but even then a fact about it was printed to the console. I know what you are thinking!
This is happening, all thanks to Inheritance! Since the parent class “Shape” has a fact() method and the parent class is being inherited by the child class “Circle”, the fact() function is getting called and displayed.
Polymorphism with Class Methods
Python can use two different class types class times in the same way. This can be demonstrated through a simple example where we create two different classes and put them in a tuple.
If we now iterate this tuple of objects, the for loop will execute all the given methods for the first object in the tuple, then the second object in the tuple and so on.
Thus, in this way, we can say that Python would be calling or using these methods without knowing or caring about the class types of the objects. This is nothing but the example of Polymorphism in Python with class methods.
To make this concept even clearer, check out the following example -
class Cat:
def mood(self):
print("Grumpy")
def sound(self):
print("Meow")
class Dog:
def mood(self):
print("Happy")
def sound(self):
print("Woof")
hello_kitty = Cat()
hello_puppy = Dog()
for pet in (hello_kitty, hello_puppy):
pet.mood()
pet.sound()
Output -
The functions mood() and sound() are same for both the classes yet they produce distinct outputs. Furthermore, we have iterated over the objects of the two classes “Cat” and “Dog” without worrying about the class types. Thus, now you know how to implement Polymorphism in Python with class methods.
Polymorphism with Function and Objects
Polymorphism in Python with functions and objects is a really interesting use-case. By defining a method and passing any object through it, we can implement Polymorphism with functions and objects in Python.
See this simple implementation -
class Beans():
def type(self):
print("Vegetable")
def color(self):
print("Green")
class Mango():
def type(self):
print("Fruit")
def color(self):
print("Yellow")
def func(obj):
obj.type()
obj.color()
#creating objects
obj_beans = Beans()
obj_mango = Mango()
func(obj_beans)
func(obj_mango)
In this code snippet, we have created two specific classes- Beans and Mango. Along with that, we have created a generic function that tells us the type and color of the object we pass. Mind you, since we have passed only “obj” through it, this obj can be any object. Next, we create two objects of the two above-mentioned classes.
Now let’s see what we get as the output -
From the output, it is clear, we were able to pass the two objects through the function really easily and also received accurate outputs. Hence, in this way we can make use of Polymorphism with classes and objects.
Data Abstraction in OOP
Abstraction is really powerful for making complex tasks and codes simpler when used in Object-Oriented Programming. It reduces the complexity for the user by making the relevant part accessible and usable leaving the unnecessary code hidden. Also, there are times when we do not want to give out sensitive parts of our code implementation and this is where data abstraction can also prove to be very functional.
From a programmer’s perspective, if we think about data abstraction, there is more to it than just hiding unnecessary information. One other way to think of abstraction is as synonymous with generalization. If, for instance, you wanted to create a program to multiply eight times seven, you wouldn't build an application to only multiply those two numbers.
Instead, you'd create a program capable of multiplying any two numbers. To put it another way, abstraction is a way of thinking about a function's specific use as separate from its more generalized purpose. Thinking this way lets you create flexible, scalable, and adaptable functions and programs. You’ll get a better understanding of data abstraction and it’s purposes by the end of this article.
Data Abstraction in Python
Data Abstraction in Python can be achieved through creating abstract classes and inheriting them later. Before discussing what abstract classes are, let us have a brief introduction of inheritance.
Inheritance in OOP is a way through which one class inherits the attributes and methods of another class. The class whose properties and methods are inherited is known as the Parent class. And the class that inherits the properties from the parent class is the Child class/subclass.
The basic syntax to implement inheritance in Python is:
class parent_class:
body of parent class
class child_class( parent_class):
body of child class
Let us now talk about abstract classes in python:
Abstract Classes in Python
Abstract Class: The classes that cannot be instantiated. This means that we cannot create objects of an abstract class and these are only meant to be inherited. Then an object of the derived class is used to access the features of the base class. These are specifically defined to lay a foundation of other classes that exhibit common behavior or characteristics.
The abstract class is an interface. Interfaces in OOP enable a class to inherit data and functions from a base class by extending it. In Python, we use the NotImplementedError to restrict the instantiation of a class. Any class having this error inside method definitions cannot be instantiated.
We can understand that an abstract class just serves as a template for other classes by defining a list of methods that the classes must implement. To use such a class, we must derive them keeping in mind that we would either be using or overriding the features specified in that class.
Consider an example where we create an abstract class Fruit. We derive two classes Mango and Orange from the Fruit class that implement the methods defined in this class. Here the Fruit class is the parent abstract class and the classes Mango and Apple become the sub/child classes. We won’t be able to access the methods of the Fruit class by simply creating an object, we will have to create the objects of the derived classes: Mango and Apple to access the methods.
Why use Abstract Base Class?
Defining an Abstract Base Class lets us create a common Application Programming Interface (API) for multiple subclasses. It is useful while working in large teams and code-bases so that all of the classes need not be remembered and also be provided as library by third parties.
Working of Abstract Class
Unlike other high-level programming languages, Python does not provide the abstract class itself. To achieve that, we use the abc module of Python, which provides the base and necessary tools for defining Abstract Base Classes (ABC). ABCs give the feature of virtual subclasses, which are classes that don’t inherit from a class and can still be recognized by
isinstance()
and
issubclass()
We can create our own ABCs with this module.
from abc import ABC
class MyABC(ABC):
pass
The abc module provides the metaclass ABCMeta for defining ABCs and a helper class ABC to alternatively define ABCs through inheritance. The abc module also provides the @abstractmethod decorator for indicating abstract methods.
ABC is defined in a way that the abstract methods in the base class are created by decorating with the @abstractmethod keyword and the concrete methods are registered as implementations of the base class.
Concrete Methods in Abstract Base Class in Python
We now know that we use abstract classes as a template for other similarly characterized classes. Using this, we can define a structure, but do not need to provide complete implementation for every method, such as:
from abc import ABC, abstractmethod
class MyClass(ABC):
@abstractmethod
def mymethod(self):
#empty body
pass
The methods where the implementation may vary for any other subclass are defined as abstract methods and need to be given an implementation in the subclass definition.
On the other hand, there are methods that have the same implementation for all subclasses as well. There are characteristics that exhibit the properties of the abstract class and so, must be implemented in the abstract class itself. Otherwise, it will lead to repetitive code in all the inherited classes. These methods are called concrete methods.
from abc import ABC, abstractmethod
class Parent(ABC):
#common function
def common_fn(self):
print('In the common method of Parent')
@abstractmethod
def abs_fn(self): #is supposed to have different implementation in child classes
pass
class Child1(Parent):
def abs_fn(self):
print('In the abstract method of Child1')
class Child2(Parent):
def abs_fn(self):
print('In the abstract method of Child2')
An abstract class can have both abstract methods and concrete methods.
We can now access the concrete method of the abstract class by instantiating an object of the child class. We can also access the abstract methods of the child classes with it. Interesting points to keep in mind are:
- We always need to provide an implementation of the abstract method in the child class even when implementation is given in the abstract class.
- A subclass must implement all abstract methods that are defined in the parent class otherwise it results in an error.
Examples of Data Abstraction
Let us take some examples to understand the working of abstract classes in Python. Consider the Animal parent class and other child classes derived from it.
from abc import ABC,abstractmethod
class Animal(ABC):
#concrete method
def sleep(self):
print("I am going to sleep in a while")
@abstractmethod
def sound(self):
print("This function is for defining the sound by any animal")
pass
class Snake(Animal):
def sound(self):
print("I can hiss")
class Dog(Animal):
def sound(self):
print("I can bark")
class Lion(Animal):
def sound(self):
print("I can roar")
class Cat(Animal):
def sound(self):
print("I can meow")
Our abstract base class has a concrete method sleep() that will be the same for all the child classes. So, we do not define it as an abstract method, thus saving us from code repetition. On the other hand, the sounds that animals make are all different. For that purpose, we defined the sound() method as an abstract method. We then implement it in all child classes.
Now, when we instantiate the child class object, we can access both the concrete and the abstract methods.
c = Cat()
c.sleep()
c.sound()
c = Snake()
c.sound()
This will give the output as:
I am going to sleep in a while
I can meow
I can hiss
Now, if we want to access the sound() function of the base class itself, we can use the object of the child class, but we will have to invoke it through super().
class Rabbit(Animal):
def sound(self):
super().sound()
print("I can squeak")
c = Rabbit()
c.sound()
This will produce the following output:
This function is for defining the sound by any animal
I can squeak
If we do not provide any implementation of an abstract method in the derived child class, an error is produced. Notice, even when we have given implementation of the sound() method in the base class, not providing an implementation in the child class will produce an error.
class Deer(Animal):
def sound(self):
pass
c = Deer()
c.sound()
c.sleep()
This will produce the following error:
Traceback (most recent call last):
File "F:/Python/Test/Parent.py", line 38, in <module>
c = Deer()
TypeError: Can't instantiate abstract class Deer with abstract methods sound
NOTE: Had there been more than one abstract method in the base class, all of them are required to be implemented in the child classes, otherwise, an error is produced.
Why Data Abstraction is Important?
Now that we know what Data Abstraction in Python is, we can also conclude how it is important.
Data Abstraction firstly saves a lot of our time as we do not have to repeat the code that may be the same for all the classes. Moreover, if there are any additional features, they can be easily added, thus improving flexibility. Not to mention, working in large teams becomes easier as one won’t have to remember every function and the basic structure can be inherited without any confusions.
Posted on May 11, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.