Object-oriented programming (OOP) is a programming paradigm that has become the most popular approach to software development in recent years.
Python, a popular programming language, also supports OOP concepts. In this article, we will explore the fundamentals of object-oriented programming in Python, including the principles of OOP.
OOP is based on four principles: encapsulation, inheritance, polymorphism, and abstraction. Let's take a look at each principle in detail.
Encapsulation
Encapsulation is one of the fundamental concepts in object-oriented programming (OOP) that helps in creating secure and robust code. In Python, encapsulation is the process of hiding the internal implementation details of a class from the outside world, and restricting access to the class's attributes and methods. This is achieved by using access modifiers, such as public, private, and protected, to control the visibility of class members.
Here's an example code snippet that demonstrates encapsulation in Python:
class Car:
def __init__(self, make, model, year):
self._make = make # protected attribute
self._model = model # protected attribute
self.__year = year # private attribute
# public method
def get_car_details(self):
return f"{self._make} {self._model} ({self.__year})"
# private method
def __get_car_age(self):
current_year = 2023
return current_year - self.__year
# create a Car object
my_car = Car("Toyota", "Camry", 2019)
# accessing protected attributes
print(my_car._make) # output: Toyota
print(my_car._model) # output: Camry
# accessing private attribute
# this will result in an AttributeError
print(my_car.__year)
# accessing public method
print(my_car.get_car_details()) # output: Toyota Camry (2019)
# accessing private method
# this will result in an AttributeError
print(my_car.__get_car_age())
In this code, we define a Car class with three attributes: make, model, and year. We use the underscore prefix to indicate that make and model are protected attributes, while the double underscore prefix is used to indicate that year is a private attribute.
The Car class also has a public method called get_car_details that returns a string with the make, model, and year of the car. Additionally, we define a private method called __get_car_age that calculates the age of the car based on the current year and the year of manufacture.
When we create a Car object, we can access the protected attributes using the underscore prefix. However, we cannot access the private attribute or private method directly. Attempting to do so will result in an AttributeError because they are not visible outside of the class.
Inheritance
Inheritance is one of the fundamental concepts of object-oriented programming (OOP) that allows us to create new classes based on existing classes. In Python, inheritance is implemented using the class keyword and the name of the parent class in parentheses.
In Python, there are three types of inheritance:
Single inheritance: This is the most common type of inheritance in Python. In single inheritance, a child class inherits from a single parent class.
Multiple inheritance: In multiple inheritance, a child class inherits from multiple parent classes.
Multi-level inheritance: In multi-level inheritance, a child class inherits from a parent class, which in turn, inherits from its own parent class.
Here are some code examples that demonstrate each type of inheritance in Python:
Single Inheritance
class Animal:
def __init__(self, name):
self.name = name
def eat(self):
print(f"{self.name} is eating.")
def sleep(self):
print(f"{self.name} is sleeping.")
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name)
self.breed = breed
def bark(self):
print(f"{self.name} is barking.")
my_dog = Dog("Buddy", "Labrador Retriever")
my_dog.eat() # output: Buddy is eating.
my_dog.bark() # output: Buddy is barking.
In this example, we have a parent class Animal and a child class Dog that inherits from the parent class. The Dog class adds a new attribute breed and a new method bark to the Animal class.
Multiple Inheritance
class Flyer:
def fly(self):
print(f"{self.name} is flying.")
class Swimmer:
def swim(self):
print(f"{self.name} is swimming.")
class Duck(Flyer, Swimmer):
def __init__(self, name):
self.name = name
my_duck = Duck("Donald")
my_duck.fly() # output: Donald is flying.
my_duck.swim() # output: Donald is swimming.
In this example, we have two parent classes Flyer and Swimmer, and a child class Duck that inherits from both parent classes. The Duck class can now access methods from both parent classes.
Multi-level Inheritance
class Animal:
def __init__(self, name):
self.name = name
def eat(self):
print(f"{self.name} is eating.")
def sleep(self):
print(f"{self.name} is sleeping.")
class Mammal(Animal):
def __init__(self, name, age):
super().__init__(name)
self.age = age
def walk(self):
print(f"{self.name} is walking.")
class Dog(Mammal):
def __init__(self, name, age, breed):
super().__init__(name, age)
self.breed = breed
def bark(self):
print(f"{self.name} is barking.")
my_dog = Dog("Buddy", 3, "Labrador Retriever")
my_dog.eat() # output: Buddy is eating.
my_dog.sleep() # output: Buddy is sleeping.
my_dog.walk() # output: Buddy is walking.
my_dog.bark() # output: Buddy is barking.
In this example, we have a parent class Animal, a child class Mammal that inherits from the Animal class, and a grandchild class Dog that inherits from the Mammal class. The Dog class adds a new attribute breed and a new method bark to the Mammal class. The Dog class can now access methods from both parent classes.
Polymorphism
Polymorphism is another important concept in object-oriented programming (OOP) that allows objects of different classes to be treated as if they were of the same class. In Python, polymorphism is implemented using method overriding and method overloading.
Method overriding is when a child class provides its own implementation for a method that is already defined in its parent class. Method overloading is when a class has multiple methods with the same name but different parameters.
Here are some code examples that demonstrate polymorphism in Python:
Method Overriding
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
pass
class Dog(Animal):
def speak(self):
return "woof"
class Cat(Animal):
def speak(self):
return "meow"
my_dog = Dog("Buddy")
my_cat = Cat("Luna")
print(my_dog.speak()) # output: woof
print(my_cat.speak()) # output: meow
In this example, we have a parent class Animal and two child classes Dog and Cat that inherit from the parent class. Both child classes override the speak method with their own implementation.
Method Overloading
class Calculator:
def add(self, x, y):
return x + y
def add(self, x, y, z):
return x + y + z
my_calc = Calculator()
print(my_calc.add(2, 3)) # output: TypeError: add() missing 1 required positional argument: 'z'
print(my_calc.add(2, 3, 4)) # output: 9
In this example, we have a class Calculator with two methods named add. The first add method takes two arguments and the second add method takes three arguments. This is an example of method overloading.
Polymorphism with Built-in Functions
print(len("hello")) # output: 5
print(len([1, 2, 3])) # output: 3
In this example, we have used the built-in function len with two different objects - a string and a list. Despite being different objects, the len function works with both of them. This is an example of polymorphism.
Abstraction
Abstraction is a fundamental concept in object-oriented programming (OOP) that allows us to hide the implementation details of a class from the outside world and only expose a simplified interface to the user. This simplifies the code and makes it easier to understand and maintain.
In Python, abstraction can be achieved using abstract classes and interfaces. An abstract class is a class that cannot be instantiated and is meant to be subclassed. An interface is a collection of abstract methods that defines a contract for its implementing classes.
Here's an example of abstraction in Python using abstract classes:
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
@abstractmethod
def perimeter(self):
pass
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius * self.radius
def perimeter(self):
return 2 * 3.14 * self.radius
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
my_circle = Circle(5)
my_rectangle = Rectangle(3, 4)
print(my_circle.area()) # output: 78.5
print(my_circle.perimeter()) # output: 31.400000000000002
print(my_rectangle.area()) # output: 12
print(my_rectangle.perimeter()) # output: 14
In this example, we have an abstract class Shape that defines two abstract methods area and perimeter. The Circle and Rectangle classes inherit from the Shape class and implement their own versions of the area and perimeter methods. The Shape class acts as an interface and provides a contract for its implementing classes.
If we try to create an instance of the Shape class directly, we will get a TypeError because it is an abstract class:
my_shape = Shape() # output: TypeError: Can't instantiate abstract class Shape with abstract methods area, perimeter