The Pillars of OOP and How They Work in Python: Part 5 Multiple Inheritance

winstonmhango23

7 min read

Views: 179

Multiple inheritance is a feature in object-oriented programming where a class can inherit attributes and methods from more than one parent class

Theory and Basics

Multiple inheritance is a feature in object-oriented programming where a class can inherit attributes and methods from more than one parent class. This allows for more flexibility in designing classes, as a class can combine behaviors from multiple sources.

Key Concepts

  1. Flexibility: Multiple inheritance allows a class to incorporate features and functionalities from more than one parent class, promoting reuse and flexibility.
  2. Complexity: It can also introduce complexity, particularly when parent classes have methods or attributes with the same name.
  3. Method Resolution Order (MRO): Python uses a specific order to determine which method to invoke when a method is called on an instance of a class that uses multiple inheritance.

Syntax and Rules

  1. Basic Syntax:
    • In Python, you can specify multiple parent classes by listing them inside the parentheses in the class definition.
class ChildClass(ParentClass1, ParentClass2):
    pass

Method Resolution Order (MRO):

  • The MRO is the order in which Python looks for a method or attribute in the hierarchy of classes. It is determined using the C3 linearization algorithm.
  • You can view the MRO of a class using the __mro__ attribute or the mro() method.
print(ChildClass.__mro__)
print(ChildClass.mro())

  1. Handling Conflicts:
    • If multiple parent classes have methods with the same name, Python follows the MRO to determine which method to call.

Practical Implementation in Python

Let's use a more complex example with a bank account system to illustrate multiple inheritance, including MRO.

Example: Complex Bank Account System

  1. Define the Parent Classes

class Account:
    def __init__(self, account_number):
        self.account_number = account_number
        self.balance = 0.0

    def deposit(self, amount):
        if amount <= 0:
            print("Deposit amount must be positive")
            return
        self.balance += amount
        print(f"Deposited {amount:.2f}, new balance is {self.balance:.2f}")

    def withdraw(self, amount):
        if amount <= 0:
            print("Withdrawal amount must be positive")
            return
        if amount > self.balance:
            print("Insufficient funds")
            return
        self.balance -= amount
        print(f"Withdrew {amount:.2f}, new balance is {self.balance:.2f}")

    def get_balance(self):
        return self.balance

    def __str__(self):
        return f"Account Number: {self.account_number}, Balance: {self.balance:.2f}"

class Customer:
    def __init__(self, name, customer_id):
        self.name = name
        self.customer_id = customer_id

    def __str__(self):
        return f"Customer Name: {self.name}, Customer ID: {self.customer_id}"

class InterestBearing:
    def __init__(self, interest_rate):
        self.interest_rate = interest_rate

    def apply_interest(self):
        if hasattr(self, 'balance'):
            self.balance += self.balance * self.interest_rate
            print(f"Interest applied, new balance is {self.balance:.2f}")
        else:
            print("This account does not have a balance attribute")

Define the Child Class with Multiple Inheritance

class SavingsAccount(Account, InterestBearing):
    def __init__(self, account_number, customer, interest_rate):
        Account.__init__(self, account_number)
        InterestBearing.__init__(self, interest_rate)
        self.customer = customer

    def __str__(self):
        return f"{Account.__str__(self)}, {self.customer}, Interest Rate: {self.interest_rate * 100}%"

Using the Multiple Inheritance Class

# Create a Customer instance
customer = Customer("John Doe", "CUST123")

# Create a SavingsAccount instance
savings_account = SavingsAccount("001", customer, 0.05)

# Perform operations on the savings account
savings_account.deposit(1000)
savings_account.apply_interest()
savings_account.withdraw(500)

# Display account details
print(savings_account)

# Display the Method Resolution Order
print(SavingsAccount.__mro__)

Detailed Explanation

Step-by-Step Breakdown

  1. Defining the Parent Classes:
    • Account: Represents a general bank account with methods for depositing, withdrawing, and getting the balance.
    • Customer: Represents a bank customer with a name and customer ID.
    • InterestBearing: Represents an entity that has an interest rate and can apply interest to a balance.
  1. Defining the Child Class with Multiple Inheritance:
    • SavingsAccount inherits from both Account and InterestBearing.
    • The constructor initializes attributes from both parent classes using Account.__init__ and InterestBearing.__init__.
    • The __str__ method combines information from both parent classes and the customer attribute to provide a complete representation of the savings account.
  1. Using the Multiple Inheritance Class:
    • An instance of Customer is created.
    • An instance of SavingsAccount is created with an account number, customer, and interest rate.
    • The deposit, apply_interest, and withdraw methods are called to demonstrate functionality.
    • The __str__ method is used to display the details of the savings account.
    • The MRO of SavingsAccount is displayed to show the order in which Python resolves method calls.

Method Resolution Order (MRO)

The MRO determines the order in which base classes are searched when executing a method. For the SavingsAccount class, the MRO can be displayed as follows:

print(SavingsAccount.__mro__)

This will output:

(<class '__main__.SavingsAccount'>, <class '__main__.Account'>, <class '__main__.InterestBearing'>, <class 'object'>)

This indicates that Python will first look in SavingsAccount, then Account, then InterestBearing, and finally object (the base class of all classes in Python).

Handling Conflicts

When multiple parent classes have methods with the same name, Python follows the MRO to determine which method to call. This ensures a predictable and consistent behavior when dealing with multiple inheritance.

Conclusion

Multiple inheritance in Python allows a class to inherit from more than one parent class, combining behaviors and attributes from multiple sources. While it offers flexibility and reuse, it also introduces complexity, especially when handling method conflicts. Python's Method Resolution Order (MRO) ensures a clear and predictable order for method resolution, making multiple inheritance manageable and useful in complex scenarios, such as the bank account example illustrated here.

Recent Related Posts

Related Posts

Creating and uploading a Python package to the PyPi: Part 3 Creating The package and uploading to pypi.org

In this part of the series, we will walk you through the final steps of creating a Python package and uploading it to PyPi. We will use the example package bank_creator that we discussed in Part 2. The full code for this package can be found on GitHub.

Read More

Creating and uploading a Python package to the PyPi: Part 2 Creating Git repository and uploading to github

Read More

Creating and uploading a Python package to the PyPi: Part 1 Basics

Read More

© 2024 .