Object-oriented programming is a programming paradigm based on the concepts of objects and classes. Objects and classes both are important paradigms of OOP. If we define animals as a class, then different types of animals are objects of that class.
Why OOP?
- Easy to debug
- It is much faster and more efficient to use.
- Better code reusability
- Better code structure
Note: I have defined the outputs in the comment against each of the code lines. Preceding from the symbol <-
is the reason for the output. You can check out the code here and don't forget to give it a 🌟 if you liked it.
Classes and Objects
A Class is a blueprint or schema which is used to define new objects. The class provides information about what properties and methods an object should have. Properties are termed as attributes while the behaviors are known as methods. When class is defined, only the description for the object is defined.
Objects are instances of the class. It is an entity that has a state and behavior. In a nutshell, it is an instance of a class that can access the data. e.g. Person is a class and Mike is an object of the Person class.
A class can be declared using the class
keyword. by default, each class has a special __init__()
method which we can override while defining the class. This special __init__()
method also known as the constructor method gets called automatically when we create new objects using that class. This process is known as instantiation. If we want to pass additional information while creating the new objects of a particular class we can accept arguments in the constructor method.
Note: If you want to check whether an object is an instance of a class or not you can use python built-in function isinstance(object_name, class_name)
which will evaluate to bool.
Example 1: Basic Structure of a Class
class Person: # class declaration
def __init__(self, name, age): # constructor method
self.name = name # attributes
self.age = age
def set_age(self, new_age): # general methods
self.age = new_age
mike = Person("Mike", 14)
print(mike.name) # Mike
mike.set_age(17)
print(mike.age) # 17
Types of Attributes
Attributes can be categorized into two types:
- Instance Attributes
- Class Attributes
class attributes are shared by all the objects of a class while the instance attributes are the exclusive property of the instance. We can access the instance attributes using the object while the class attributes can be accessed using the Class. We can also access the class attribute using the object by accessing the __class__
property. the class attribute is also known as the static attribute.
Example 2: How to access Attributes
class Person:
# class attribute
class_name = "Person"
def __init__(self, name, age):
# instance attribute
self.name = name
self.age = age
mike = Person("Mike", 15)
print(mike.age) # 15 <- Access the simple members
print(Person.class_name) # Person <- Accessing class variables using class
print(mike.__class__.class_name) # Person <- Accessing class variables using object
Types of Methods
Python offers three types of methods namely
- Instance Methods
- Static Methods
- Class Methods
Let’s peek into what happens behind the scenes for each method type.
Instance methods receive the instance of the class as an argument generally termed as self. It can take any number of arguments. Using the self
parameter we can access the other attributes and methods of the class.
Note: using self.__class__
, we can access the class methods and class attributes also.
Class methods are defined using the special decorator @classmethod
. A class method can only have access to the class attributes but not to the instance attributes. They are bound to the class, not to the object. Similar to the instance method, a class method accepts the class itself as an argument generally termed as cls.
A Static method can neither modify the object state nor the class state. They are primarily a way to namespace our methods. Static methods are flagged as static by using the @staticmethod
decorator. Static methods can be created for a function to be called on a specific type of object.
Example 3: Methods available in Python OOPs
from datetime import date
class Person:
class_name = "Person"
def __init__(self, name, age):
self.name = name
self.age = age
# instance method
def print_name(self):
print(self.name)
# class method
@classmethod
def create_person_from_birthyear(cls, name, birth_year):
print("Creating a new object of type:", cls.class_name)
return cls(name, date.today().year - birth_year)
# static method
@staticmethod
def is_adult(age):
return age > 18
mike = Person("Mike", 15)
mike.print_name() # Mike <- Instance Method
james = Person.create_person_from_birthyear("James", 2001) <- Object creation using classmethod
james.print_name() # James
Person.is_adult(james) # True <- Static Method
Access modifiers
The access modifiers are used to modify the default scope of variables. There are three types of access modifiers in Python: public, private, and protected.
Variables with the public access modifiers can be accessed anywhere inside or outside the class, the private variables can only be accessed inside the class, while protected variables can be accessed within the same package.
To create a private variable, you need to prefix double underscores with the name of the variable. To create a protected variable, you need to prefix a single underscore with the variable name. For public variables, you do not have to add any prefixes at all.
Example 4: Access Modifiers Example
class Person:
def __init__(self, name, age, salary):
self.name = name # public member
self._age = age # protected member
self.__salary = salary # private member
mike = Person("Mike", 17, 15000)
print(mike.name) # Mike
print(mike._age) # 17
print(mike.__salary) # Attribute Error
Note: If you want to access or change the private class member you can do it by objectName._className__memberName = new_value
but it is avoided.
print(mike._Person__salary) # 15000 <- Accessing private variable outside
mike._Person__salary = 25000 <- Changing value of private variable
print(mike._Person__salary) # 25000
Pillars of OOPs
The whole OOPs paradigm is based upon three major pillars named as follows:
- Inheritance
- Polymorphism
- Encapsulation
Inheritance
Inheritance in object-oriented programming is pretty similar to real-world inheritance where a child inherits some of the characteristics from his parents, in addition to his/her unique characteristics. The basic idea of inheritance in object-oriented programming is that a class can inherit the characteristics of another class.
Example 5: Inheritance
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def read_age(self):
print(self.age)
class Employee(Person):
def __init__(self, name, age, position, salary):
super().__init__(name, age) # can be Person.__init__(self, name, age)
self.position = position
self.salary = salary
def read_salary(self):
print(self.salary)
employee1 = Employee("Mike", 23, "Software Engineer", 999999)
employee1.read_age() # 23 <- Method present in the parent class
employee1.read_salary() # 999999 <- Method present in child class
To implement inheritance in python while declaring the child class we pass the name of the base class as the argument. Now to inherit the properties and methods of the base class one has to call the super().__init__(*similar_arguments)
function which in the case of single Inheritance can be replaced with
baseClassName.__init__(self, *similar_arguments)
and
In case of multiple inheritances can be replaced by a series of baseClassName.__init__(self, *similar_arguments)
. Python allows multiple inheritances.
Note:
- by default, each class in python inherits the
object
class. - If you want to check whether a class_B has inherited class_A you can use python built-in function
issubclass(class_B, class_A)
which evaluates to bool.
Polymorphism
In the context of object-oriented programming, polymorphism refers to the ability of an object to behave in multiple ways. It is also known as method overriding where we change the definition of the predefined method.
Example 6: Polymorphism
class Vehicle:
def print(self):
print("This is parent class Vehicle")
class Car(Vehicle):
def print(self):
print("This is child class Car")
class Cycle(Vehicle):
def print(self):
print("This is child class Cycle")
vehicle = Vehicle()
car = Car()
cycle = Cycle()
vehicle.print() # This is parent class Vehicle <- Method called from Vehicle class
car.print() # This is child class Car <- Method called from Car class
cycle.print() # This is child class Cycle <- Method called from Cycle class
Encapsulation
Encapsulation is the third pillar of object-oriented programming. Encapsulation simply refers to data hiding. For implementing this property we can use private members in the class and provide an interface for accessing and changing the members by implementing getter and setter methods in the class. A pythonic way to handle this type of situation is using the @property
decorator. you can check that out here.
Example 7: Encapsulation
class Price:
def __init__(self, price):
self.__price = price
def get_price(self):
print("Price is", self.__price)
def set_price(self, new_price):
self.__price = new_price
amount = Price(-99)
amount.get_price() # Price is -99
amount.set_price(99)
amount.get_price() # Price is 99
print(amount.__price) # Attribute Error <- evaluates to error as private members can't be accessed directly
The Abstract Class
An abstract class is a class, but you can't create objects from it directly. Its purpose is to define how other classes should look like, i.e. what methods and properties they are expected to have. The methods and properties defined in an abstract class are called abstract methods and abstract properties. An abstract method is a method that is declared but contains no implementation. Abstract classes cannot be instantiated, and require subclasses to provide implementations for the abstract methods. If any of the methods defined in the abstract class is not overridden in child one then it will throw an error. In python, we can create an abstract class by inheriting the ABC
class from the abc
module.
Example 8: Abstract Class
from abc import ABC, abstractmethod
class AbstractClassExample(ABC):
@abstractmethod
def do_something(self):
pass
class AnotherSubclass(AbstractClassExample):
def do_something(self):
super().do_something()
print("The changes from AnotherSubclass")
x = AnotherSubclass()
x.do_something() # The changes from AnotherSubclass <- Implementation found in the child class
The MetaClass
A metaclass is a class whose instances are classes. Like an ordinary class defines the behavior of the instances of the class, a metaclass defines the behavior of classes and their instances. You can learn more about metaclasses here.
Operator Overloading
Class functions that begin with double underscore __
are called special functions in Python. For the custom classes, we can change the definition of built-in python operators using the special methods. for a custom class, we can change the definition of print()
function using the __str__()
special method. we can even make an object callable using the special method __call__()
. There are plenty of special methods available. Special methods are known as magic methods or dunder methods too.
Example 9: Operator Overloading
class ComplexNumber:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
if self.y >= 0:
return "{}+{}i".format(self.x, self.y)
else:
return "{}{}i".format(self.x, self.y)
def __add__(self, c):
self.x = self.x + c.x
self.y = self.y + c.y
c1 = ComplexNumber(5, 3)
c2 = ComplexNumber(5, -3)
print(c1) # 5+3i
print(c2) # 5-3i
c1 + c2 # <- gets evaluated to c1.__add__(c2)
print(c1) # 10+0i
Conclusion
Thanks for reading. I hope it will help you a lot. If you like it, don’t forget to follow me to get more amazing articles about programming and technologies! for any queries related to the blog ping me at adarshsrivastava.tech@gmail.com 😊.