Course Content
How to Add a Virtual Environment in Python
0/1
Set
0/1
Magic Methods
0/1
Python
About Lesson

Inheritance is one of the core principles of Object-Oriented Programming (OOP), and it allows one class (the child or subclass) to inherit the properties and behaviours (attributes and methods) of another class (the parent or superclass). This promotes code reuse and improves the maintainability and extensibility of software.

When a subclass inherits from a superclass:

  • The subclass automatically gains all the attributes and methods from the superclass.
  • The subclass can extend or override the functionality of the superclass.

Example:

Python
# Parent class (Base class)
class Animal:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        return "Some generic sound"

# Child class (Derived class)
class Dog(Animal):
    def __init__(self, name, breed):
        # Call the parent class constructor using super()
        super().__init__(name)
        self.breed = breed
    
    def speak(self):
        return f"{self.name} barks."

# Creating objects
dog = Dog("Buddy", "Golden Retriever")
print(dog.speak())  # Output: Buddy barks.
  • super() is used to call the methods of the parent class from the child class. It is particularly useful in the constructor method (__init__) when you need to initialize the parent class.

Example:

Python
class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        return "Some generic sound"

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)  # Calls the __init__ method of the parent class
        self.breed = breed

    def speak(self):
        return f"{self.name} barks"

# Create an instance of Dog
dog = Dog("Charlie", "Labrador")
print(dog.speak())  # Output: Charlie barks

In the example above:

  • The Dog class inherits from the Animal class.
  • The Dog class uses super().__init__(name) to initialize the name attribute inherited from the Animal class.

Method overriding occurs when a subclass defines a method with the same name as a method in the parent class, but with a different implementation.

This allows the subclass to provide its own specific functionality for methods inherited from the superclass.

Python
class Animal:
    def speak(self):
        return "Animal makes a sound."

class Dog(Animal):
    def speak(self):  # Overriding the speak method of the parent class
        return "Dog barks."

# Creating an object of Dog
dog = Dog()
print(dog.speak())  # Output: Dog barks.

In this example:

  • The Dog class overrides the speak method of the Animal class to provide its own implementation.

In Python, a class can inherit from multiple classes, which is called multiple inheritance. This allows a class to inherit attributes and methods from more than one parent class.

Python
class Animal:
    def speak(self):
        return "Some sound"

class Mammal:
    def walk(self):
        return "Walking on land"

class Dog(Animal, Mammal):  # Inheriting from both Animal and Mammal
    def bark(self):
        return "Barking"

dog = Dog()
print(dog.speak())  # Output: Some sound
print(dog.walk())   # Output: Walking on land
print(dog.bark())   # Output: Barking

In this case:

  • The Dog class inherits from both the Animal and Mammal classes.
  • It has access to methods speak from Animal and walk from Mammal.

In Python, there are no strict access modifiers like in some other languages (e.g., private, protected). However, Python uses a naming convention to indicate the intended visibility of attributes and methods:

  • Public members: No leading underscores (attribute or method).
  • Protected members: A single leading underscore (_attribute).
  • Private members: A double leading underscore (__attribute).

Accessing Private and Protected Attributes

  • Private attributes can be accessed directly within the class, but they are not intended to be accessed from outside the class.
  • Protected attributes are intended to be accessible from subclasses, but it’s a convention to treat them as private.
Python
class Animal:
    def __init__(self, name):
        self.__name = name  # Private attribute
        self._species = "Mammal"  # Protected attribute

    def get_name(self):
        return self.__name  # Accessing private attribute via a method

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)
        self.breed = breed

    def display_info(self):
        return f"{self.get_name()} is a {self.breed}."

# Creating an object of Dog
dog = Dog("Buddy", "Golden Retriever")
print(dog.display_info())  # Output: Buddy is a Golden Retriever

In this example:

  • The __name attribute is private in the Animal class, so it cannot be accessed directly outside the class, but we use the get_name method to retrieve it.
  • The _species attribute is protected, which means it’s meant to be accessed within the class or its subclasses, but it’s not strictly private.

An abstract class is a class that cannot be instantiated directly and typically contains abstract methods that must be implemented by subclasses. It provides a template for other classes to inherit from.

  • To define an abstract class, you need to import the abc module and use the ABC class as a base class, along with the @abstractmethod decorator to mark methods as abstract.
Python
from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def speak(self):
        pass

class Dog(Animal):
    def speak(self):
        return "Woof!"

# The following will raise an error since Animal cannot be instantiated directly
# animal = Animal()  # Error: Can't instantiate abstract class Animal with abstract methods speak

# Creating an instance of Dog (since it implements the speak method)
dog = Dog()
print(dog.speak())  # Output: Woof!
  • Code Reusability: Subclasses can reuse code from the parent class.
  • Extensibility: New features can be added to existing classes without modifying the original code.
  • Maintainability: Changes to the parent class automatically propagate to subclasses.
  • Polymorphism: Subclasses can provide different implementations of methods that have the same name, allowing for flexible behavior.