One of the main features of OOP is inheritance, Encapsulation,Polymorphism
                     
    
Inheritance
One of the main features of OOP is inheritance, where a class can inherit attributes and methods from another class. This allows you to reuse code and create more specialized classes.
 Example: Inheriting from a Parent Class
 Parent class (Base class)
class Animal:
    def __init__(self, name):
        self.name = name
    def speak(self):
        print(f"{self.name} makes a sound.")
 Child class (Derived class)
class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)   Call the parent class constructor
        self.breed = breed
    def speak(self):
        print(f"{self.name} says woof!")
 Create instances
dog = Dog("Buddy", "Golden Retriever")
dog.speak()   Output: Buddy says woof!
In this example, `Dog` inherits from the parent class `Animal`. The `super()` function allows the child class to call methods from the parent class.
 Encapsulation
Encapsulation is the concept of hiding the internal state of an object and restricting access to certain methods or attributes. This is done by using private and public access specifiers.
 Public attributes and methods can be accessed from outside the class.
 Private attributes and methods are hidden from outside access and are indicated by a leading underscore (`_`) or double underscore (`__`).
class BankAccount:
    def __init__(self, balance):
        self.__balance = balance   Private attribute
     Method to deposit money
    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount
        else:
            print("Deposit amount must be positive.")
     Method to get the balance
    def get_balance(self):
        return self.__balance
 Create an account instance
account = BankAccount(1000)
 Accessing private attribute directly would raise an error
 print(account.__balance)   AttributeError
 Using methods to access or modify private attribute
account.deposit(500)
print(account.get_balance())   Output: 1500
Polymorphism
Polymorphism allows you to use methods in different ways based on the object’s class. It is achieved through method overriding (in inheritance) or method overloading (though Python doesn't support method overloading in the traditional sense).
 Example: Polymorphism in Action
class Bird:
    def speak(self):
        print("Tweet!")
class Dog:
    def speak(self):
        print("Woof!")
 Create instances
bird = Bird()
dog = Dog()
 Both classes have a 'speak' method, but they behave differently
for animal in (bird, dog):
    animal.speak()   Output: Tweet! Woof!
In this case, both `Bird` and `Dog` classes have a `speak` method, but the output is different depending on the object calling it.
 Conclusion
Object-Oriented Programming (OOP) in Python is a powerful paradigm that helps you organize your code into logical, reusable components. By mastering the following OOP concepts, you can write more efficient, readable, and maintainable code:
* Classes and Objects
* Instance and Class Attributes
* Methods
* Inheritance
* Encapsulation
* Polymorphism