The first object-oriented language (generally agreed to be Simula) introduced the idea of objects. Objects are collections of information that are treated as a singular entity.
We'll dive deeper into what that actually means in a second with an example, but first we have to talk about classes. Classes are sort of like pre-objects. They contain a list of attributes that, when defined, become an object.
OOP's CONCEPTS :
CLASS & OBJECT
The class can be defined as a collection of objects. It is a logical entity that has some specific attributes and methods. For example: if you have an employee class, then it should contain an attribute and method, i.e. an email id, name, age, salary, etc.
Syntax
class ClassName:
<statement-1>
.
<statement-N>
The object is an entity that has state and behavior. It may be any real-world object like the mouse, keyboard, chair, table, pen, etc.
Everything in Python is an object, and almost everything has attributes and methods. All functions have a built-in attribute __doc__, which returns the docstring defined in the function source code.
When we define a class, it needs to create an object to allocate the memory. Consider the following example.
Example:
class car:
def __init__(self,modelname, year):
self.modelname = modelname
self.year = year
def display(self):
print(self.modelname,self.year)
c1 = car("Toyota", 2016)
c1.display()
Output:
Toyota 2016
In the above example, we have created the class named car, and it has two attributes modelname and year. We have created a c1 object to access the class attribute. The c1 object will allocate memory for these values. We will learn more about class and object in the next tutorial.
The method is a function that is associated with an object. In Python, a method is not unique to class instances. Any object type can have methods.
DATA ABSTRACTION
What is Abstraction
Abstraction means hiding the complexity of the implementation and just exposing the essential features to the user. As an example you can take any electronics item where you interact with the product using buttons and switches to turn it on and off or increase and decrease the volume or speed. The real complexity, how that functionality is implemented is hidden from us.
In the context of object oriented programming Abstraction means exposing just the end points (methods) and hiding the real implementation from the end user.
Abstraction in Python
Abstraction in Python is achieved by using abstract classes and interfaces.
Abstract class is a class that contains one or more abstract methods. Abstract methods are the methods that don’t contain any implementation, sub classes that inherit from the abstract class should provide implementation for the abstract methods. Abstract class can have regular methods (methods with method body) too so you can say that Abstract class generally provides incomplete functionality providing implementation for the common methods while leaving the specific implementation to the sub-classes.
An interface provides just the method signatures without method bodies. Sub-classes should provide implementation for all the methods defined in an interface. Python doesn’t support creation of interface through any separate keyword, you will have to define an interface using abstract class itself. If you create an abstract class which contains only abstract methods that acts as an interface in Python.
Abstraction in Python using abstract class
Let’s see an example of abstraction in Python using an abstract class. For declaring an Abstract class you need to import the abc module.
In the example we have an Abstract class User that has one concrete method display_user() and one abstract method process_fee().
from abc import ABC, abstractmethod
class User(ABC):
def __init__(self, name, num_of_months):
self.name = name
self.num_of_months = num_of_months
# concrete method
def display_user(self):
print('User %s subscribed for %d months' % (self.name, self.num_of_months))
# abstract method
@abstractmethod
def process_fee(self):
pass
There are two sub classes inheriting from User and implementing the abstract method process_fee().
class PlatinumUser(User):
PLATINUM_PACKAGE = 2200
def process_fee(self):
return self.num_of_months * PlatinumUser.PLATINUM_PACKAGE
class GoldUser(User):
Gold_PACKAGE = 1500
def process_fee(self):
return self.num_of_months * GoldUser.Gold_PACKAGE
As a user we just know that we have to call process_fee() method, we are abstracted from the actual implementation of the method which differs for different child classes of User.
obj = PlatinumUser('Mike Dallas', 8)
obj.display_user()
fee = obj.process_fee()
print('Fee is', fee)
obj = GoldUser('Goldie Hawn', 6)
obj.display_user()
fee = obj.process_fee()
print('Fee is', fee)
obj = PlatinumUser('Ashish Mishra', 10)
obj.display_user()
fee = obj.process_fee()
print('Fee is', fee)
Output
User Mike Dallas subscribed for 8 months
Fee is 17600
User Goldie Hawn subscribed for 6 months
Fee is 9000
User Ashish Mishra subscribed for 10 months
Fee is 22000
POLYMORPHISM
What is Polymorphism
Polymorphism is a Greek word where poly means “many” and morph means “change from one form to another”. In object oriented terms it relates to the same object reference taking many forms (assigned different types), a method with the same name having more than one implementations, an operator behaving differently for different operands.
Polymorphism in Python
In an object oriented language you may see use of Polymorphism in one of the following ways
Method overloading, also known as compile time Polymorphism
Method overriding, also known as run time Polymorphism
Operator overloading
In Python you will find support for Polymorphism through Method overriding and Operator overloading. Python doesn’t support method overloading in its traditional sense though. Also when discussing Polymorphism in Python you should also get to know about duck typing in Python. So let’s see some examples.
Compile time polymorphism (Method Overloading)
Method overloading means having multiple methods with the same name in a class. These overloaded methods differ in types or number of arguments passed.
Python doesn’t support compile time polymorphism or method overloading. If there are multiple methods with the same name in a class only the last defined method is recognized. Calling any other overloaded method results in an error.
Read more about Method Overloading in Python in this post- Method Overloading in Python With Examples
Runtime polymorphism (Method Overriding)
In case of inheritance child class inherits all the properties and methods of parent class. If there is a method in a child class that has the same name and same number of arguments as in parent class then this process is called method overriding where the child class method is said to be overriding the parent class method.
Read more about Method Overriding in Python in this post- Method Overriding in Python With Examples
When you call the overridden method with parent class object, method of the parent class is executed. When same method is called with child class object, method of the child class is executed. So the appropriate overridden method is called based on the object type, which is an example of run time polymorphism.
Consider the following class hierarchy where super class Animal has two methods info() and make_sound(). There are two child classes Duck and Dog overriding both of the methods.
class Animal:
def info(self):
print('I am an animal')
def make_sound(self):
pass
class Duck(Animal):
def info(self):
print('I am a Duck')
def make_sound(self):
print('Quack Quack')
class Dog(Animal):
def info(self):
print('I am a Dog')
def make_sound(self):
print('Bow Wow')
d = Duck()
d.info()
d.make_sound()
d = Dog()
d.info()
d.make_sound()
Output
I am a Duck
Quack Quack
I am a Dog
Bow Wow
When d refers to an object of Duck it calls the method of Duck class, when d refers to an object of Dog class it calls the method of that class.
Polymorphism in Python through operator overloading
Operator overloading is also an example of polymorphism where the same operator performs different operations based on the type of operands. Python supports operator overloading.
For example ‘+’ operator-
When used with numbers it performs addition operation.
When used with two strings concatenate those strings
When used with lists merge those lists
# + operator with integers- Addition
print(3 + 4)
# + operator with Strings- Concatenation
print("Operator " + "Overloading")
a = [10, 11, 12]
b = [100, 200, 300]
# + operator with Lists- Merging
print(a + b)
Output
7
Operator Overloading
[10, 11, 12, 100, 200, 300]
Duck typing and Polymorphism
Python follows the duck typing philosophy which states “If it walks like a duck, and it quacks like a duck, then it must be a duck”.
In a dynamic language like Python it means that you don’t worry about the type or the class of an object. You can perform the required action with the object or not is more important.
Because of this duck typing principle followed by Python it is possible to create a function that can take any object, allowing for polymorphism. As long as the passed object has the called method it can be called. Let’s clear it with an example where we have two classes with a method make_sound(). There is a function invoke which takes any object as an argument and calls the make_sound() method on that object. When invoke function is called with an object of class Duck make_sound() method of the Duck class is called, when it is called with an object of class Dog make_sound() method of the Dog class is called.
class Duck:
def make_sound(self):
print("Quack Quack")
class Dog:
def make_sound(self):
print('Bow Wow')
def invoke(obj):
obj.make_sound()
d = Duck()
invoke(d)
d = Dog()
invoke(d)
Output
Quack Quack
Bow Wow
ENCAPSULATION
What is Encapsulation
Encapsulation is the process of keeping the data and the code (methods) that manipulates that data together as a unit. Any variable can only be changed through a method in the same class that way data is protected from any intentional or accidental modification by any outside entity.
A class is an example of encapsulation as it wraps all the variables and methods defined with in that class.
Encapsulation in Python
Since class is an example of Encapsulation so defining a class in Python which wraps all the variables and methods is first step towards encapsulation. But the question is how to stop outside access to the variables as there are no explicit access modifiers like public, private, protected in Python and all the variables are public by default. Here is an example to clarify it-
class User:
def __init__(self, name, age):
self.name = name
self.age = age
def display_user(self):
print('User Name:', self.name)
print('User Age:', self.age)
user = User('Mike Dallas', 34)
# calling class method
user.display_user()
# Accessing variables directly
print(user.name)
print(user.age)
Output
User Name: Mike Dallas
User Age: 34
Mike Dallas
34
As you can see name and age fields of the class User can be accessed through a class method as well as directly outside the class too.
How to control access in Python
As demonstrated through an example class fields can be accessed directly from outside the class in Python so how to control that access and how to have proper encapsulation in Python?
Python has the concept of using a variable prefixed with a single underscore (e.g. _name) and a variable prefixed with double underscores (e.g. __name) to give some semblance to controlling access with in a class.
Using single underscore
Prefixing a variable with a single underscore is merely a convention followed in Python code to show your intention that such a class member should be treated as a non-public part of the API (whether it is a function, a method or a data member). It is more of an indicator to other developers that such a class member should be used only with in the scope of the class and shouldn’t be accessed from outside the class.
Since using a single underscore is just a convention so it doesn’t actually change the access of the variable in any way.
class User:
def __init__(self, name, age):
self.name = name
self._age = age
def display_user(self):
print('User Name:', self.name)
print('User Age:', self._age)
user = User('Mike Dallas', 34)
# calling class method
user.display_user()
# Accessing variables directly
print(user.name)
print(user._age)
Output
User Name: Mike Dallas
User Age: 34
Mike Dallas
34
You can see that the age variable is now prefixed with a single underscore but that can still be accessed outside the class. You will get this warning though ‘Access to a protected member _age of a class’.
Using double underscore
You can come closest to making a class member private in Python by prefixing it with double underscores. This process is known as name mangling in Python where any identifier of the form __var (at least two leading underscores, at most one trailing underscore) is textually replaced with _classname__var by the Python interpreter, where classname is the current class name.
class User:
def __init__(self, name, age):
self.name = name
self.__age = age
def display_user(self):
print('User Name:', self.name)
print('User Age:', self.__age)
user = User('Mike Dallas', 34)
# calling class method
user.display_user()
# Accessing variables directly
print(user.name)
print(user.__age)
Output
User Name: Mike Dallas
User Age: 34
Mike Dallas
Traceback (most recent call last):
File "F:/knpcode/Programs/Example.py", line 16, in
print(user.__age)
AttributeError: 'User' object has no attribute '__age'
INHERITANCE
Inheritance concept
Inheritance allows us to create a class that acquires, all the properties and methods of another class.
The class whose members are inherited is called the Super class. Also known as parent class or base class.
The class that inherits from another class is called the Sub class. Also known as child class or derived class.
Python inheritance syntax
If there is a class called ParentClass defined as-
class ParentClass:
body of parent class
Then a ChildClass that inherits from this ParentClass can be defined as-
class ChildClass(ParentClass):
body of child class
Inheritance Python example
In the example there is a class called Person that acts as a base class and another class Employee that inherits from Person class.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def display_person(self):
print('In display_person method')
print('Name:', self.name)
print('Age:', self.age)
class Employee(Person):
pass
e = Employee("Michael Weyman", 42)
e.display_person()
Output
In display_person method
Name: Michael Weyman
Age: 42
As you can see in Employee class just pass keyword is used to specify that it doesn’t add any property or method to a class. It just inherits all the properties and methods of the class it inherits from.
You can create an object of Employee class and initialize the ‘name’ and ‘age’ properties because Employee class inherits these properties from Person class. Same way you can also call the method display_person() method using the object of Employee class.
Constructor overriding and use of super with inheritance
When a class inherits another class in Python, by default constructor of the super class is also available to the child class. If you have extra fields in the child class which you need to initialize in the child class then you can override the constructor in the child class to initialize the fields there.
In most of the scenarios you’ll inherit from a base class and add properties and methods of its own in the child class as well. To initialize the properties of the child class you can add __init__() function in the child class too. In our Employee class let’s add two fields person_id and department and add a method display_employee() too.
class Employee(Person):
def __init__(self, person_id, department, name, age):
self.name = name
self.age = age
self.person_id = person_id
self.department = department
def display_employee(self):
print('In display_employee method')
print('Id:', self.person_id)
print('Name:', self.name)
print('Age:', self.age)
print('Department:', self.department)
In the above class you can notice the redundancy of initializing the parent class’ fields in the constructor though there is a constructor in the parent class which is already doing that. Same way in the display_employee () method we have print statements to print name and age too though there is a method in Person class which is already doing that.
If you want to call super class constructor and methods from sub-class that can be done using super() function which helps in avoiding code redundancy as present in the above Employee class. Here is the modified Employee class with usage of super() function.
class Employee(Person):
def __init__(self, person_id, department, name, age):
# call constructor of super class
super().__init__(name, age)
self.person_id = person_id
self.department = department
def display_employee(self):
# call method of super class
super().display_person()
print('In display_employee method')
print('Id:', self.person_id)
print('Department:', self.department)
e = Employee(1, "IT", "Michael Weyman", 42)
e.display_employee()
Output
In display_person method
Name: Michael Weyman
Age: 42
In display_employee method
Id: 1
Department: IT
Advantages of inheritance
Inheritance helps in writing reusable code where you can use the existing functionality just by inheriting from an existing class.
Inheritance helps in writing hierarchical code where you write more generalized code in the super class and then move on to inherit it and add more specific methods. For example you can have a Vehicle super class with more generic functionality like accelerate(), brake(), gear(). Then inherit it to create more specialized classes like Car, Bus, MotorCycle and further down to inherit from Car to create more specific classes like SUV, SportsCar.
Also makes managing the code easy because you don’t put all the functionality in the same class you rather create several classes to create a hierarchical structure with code distributed among those classes.