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
- Flexibility: Multiple inheritance allows a class to incorporate features and functionalities from more than one parent class, promoting reuse and flexibility.
- Complexity: It can also introduce complexity, particularly when parent classes have methods or attributes with the same name.
- 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
- 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())
- 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
- 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
- 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.
- 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.
- 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.