Object-Oriented Programming (OOP) is a programming paradigm based on the concept of "objects," which are instances of classes. OOP in Python helps in organizing and structuring the code, making it easier to manage and extend. This guide will explore the key principles of OOP with Python examples: Inheritance, Polymorphism, Encapsulation, Composition and Abstraction.
Classes and Objects
In Python, classes serve as blueprints for creating objects. An object is an instance of a class.
class Car: def __init__(self, make, model, year): self.make = make self.model = model self.year = year def display_info(self): print(f"{self.year} {self.make} {self.model}") my_car = Car('Mitsubishi', 'Lancer', 2008) my_car.display_info() # 2008 Mitsubishi Lancer
Inheritance
Inheritance allows a class (called a subclass) to inherit attributes and methods from another class (called a superclass). It promotes code reuse and can extend functionality.
class Animal: def __init__(self, name): self.name = name def speak(self): print(f"{self.name} makes a sound") class Dog(Animal): def speak(self): print(f"{self.name} barks") class Cat(Animal): def speak(self): print(f"{self.name} meows") # Creating objects dog = Dog("Buddy") dog.speak() # Buddy barks cat = Cat("Whiskers") cat.speak() # Whiskers meows
- Key Concepts in Inheritance:
class Animal: def __init__(self, name): self.name = name def speak(self): print(f"{self.name} makes a sound") class Dog(Animal): def __init__(self, name, breed): super().__init__(name) # Call the constructor of Animal self.breed = breed def speak(self): print(f"{self.name} ({self.breed}) barks") dog = Dog("Buddy", "Bulldog") dog.speak() # Buddy (Bulldog) barks
Polymorphism
Polymorphism allows objects of different classes to be treated as objects of a common superclass. The most common use of polymorphism is when a subclass overrides a method in the superclass.
class Animal: def __init__(self, name): self.name = name def speak(self): print(f"{self.name} makes a sound") class Cat(Animal): def speak(self): print(f"{self.name} meows") class Dog(Animal): def speak(self): print(f"{self.name} barks") class Bird(Animal): def speak(self): print(f"{self.name} chirps") animals = [Dog("Buddy"), Cat("Whiskers"), Bird("Tweety")] for animal in animals: animal.speak() # Buddy barks # Whiskers meows # Tweety chirps
Encapsulation
Encapsulation involves bundling data (attributes) and methods that operate on the data into a single unit, i.e., a class. It also helps restrict direct access to some of the object's components, which is done by using private and protected attributes.
class BankAccount: def __init__(self, owner, balance=0): self.owner = owner self.__balance = balance # Private attribute def deposit(self, amount): if amount > 0: self.__balance += amount else: print("Deposit amount must be positive!") def withdraw(self, amount): if 0 < amount <= self.__balance: self.__balance -= amount else: print("Invalid withdrawal amount!") def get_balance(self): return self.__balance account = BankAccount("Alice", 1000) account.deposit(500) account.withdraw(200) print(account.get_balance()) # 1300 # Direct access to __balance would raise an error # print(account.__balance) # AttributeError
Composition
Composition involves using instances of other classes as attributes to represent relationships between objects. It is an alternative to inheritance and emphasizes “has-a” relationships.
class Engine: def start(self): print("Engine starting...") class Car: def __init__(self, make, model): self.make = make self.model = model self.engine = Engine() # Car "has-a" Engine def start(self): print(f"Starting {self.make} {self.model}...") self.engine.start() my_car = Car("Honda", "Civic") my_car.start() # Starting Honda Civic... # Engine starting...
Abstraction
Abstraction allows hiding the complexity of the system by exposing only the essential parts. In Python, abstract classes can be used to achieve abstraction. Abstract classes cannot be instantiated and must have at least one abstract method.
from abc import ABC, abstractmethod class Shape(ABC): @abstractmethod def area(self): pass class Circle(Shape): def __init__(self, radius): self.radius = radius def area(self): return 3.14 * (self.radius ** 2) class Rectangle(Shape): def __init__(self, width, height): self.width = width self.height = height def area(self): return self.width * self.height # Create objects circle = Circle(5) rectangle = Rectangle(4, 6) print(circle.area()) # 78.5 print(rectangle.area()) # 24
Class vs Instance Variables
class Person: species = "Homo sapiens" # Class variable def __init__(self, name, age): self.name = name # Instance variable self.age = age # Instance variable # Create objects p1 = Person("Alice", 30) p2 = Person("Bob", 25) print(p1.species) # Homo sapiens print(p2.species) # Homo sapiens p1.species = "Homo sapiens sapiens" # Modifying class variable for p1 only print(p1.species) # Homo sapiens sapiens print(p2.species) # Homo sapiens
Method Resolution Order (MRO)
In the case of multiple inheritance, Python uses the C3 linearization algorithm to determine the order in which methods are inherited from multiple classes.
class A: def hello(self): print("Hello from A") class B(A): def hello(self): print("Hello from B") class C(A): def hello(self): print("Hello from C") class D(B, C): pass # Create object of class D obj = D() obj.hello() # Hello from B print(D.mro()) # [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]