0% found this document useful (0 votes)
5 views10 pages

Multiple Inheritance

Multiple inheritance in Python allows a class to inherit from multiple parent classes, enabling flexible code design but introducing complexity, particularly with method resolution. The Method Resolution Order (MRO) determines the order in which classes are searched for methods and attributes, using the C3 linearization algorithm to avoid ambiguity and support cooperative inheritance. Challenges include name conflicts and the diamond problem, which can be managed through careful design and the use of the super() function.

Uploaded by

andreasclinton73
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
5 views10 pages

Multiple Inheritance

Multiple inheritance in Python allows a class to inherit from multiple parent classes, enabling flexible code design but introducing complexity, particularly with method resolution. The Method Resolution Order (MRO) determines the order in which classes are searched for methods and attributes, using the C3 linearization algorithm to avoid ambiguity and support cooperative inheritance. Challenges include name conflicts and the diamond problem, which can be managed through careful design and the use of the super() function.

Uploaded by

andreasclinton73
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Multiple Inheritance

Multiple inheritance is a feature in object-oriented programming where a class can inherit attributes and methods from
more than one parent class. In Python, this allows a subclass to combine behaviours and attributes from multiple
superclasses, enabling flexible and modular code design. However, it also introduces complexity, which we’ll address
through the Method Resolution Order (MRO).

Objectives

• Understand multiple inheritance and its use cases.

• Understand the Method Resolution Order (MRO) and its significance.

What is Multiple Inheritance?

In Python, a class can inherit from multiple parent classes by listing them in the class definition. This allows the subclass
to access attributes and methods from all parent classes.

Syntax

class Parent1:
def method1(self):
print("Method from Parent1")

class Parent2:
def method2(self):
print("Method from Parent2")

class Child(Parent1, Parent2):


pass

# Example usage
child = Child()
child.method1() # Output: Method from Parent1
child.method2() # Output: Method from Parent2

Use Cases

i. Combining unrelated behaviors

ii. Modeling complex real-world relationships (e.g., a Flying Car inheriting from Car and Airplane).

iii. Enhancing modularity with reusable components (mixins).

Challenges

i. Name Conflicts: When multiple parent classes define methods or attributes with the same name.

ii. Complexity: Deep inheritance hierarchies can make code harder to understand and maintain.

iii. Ambiguity in Method Resolution: Determining which parent class’s method is called when names overlap.
Classwork

Question 1

Combining unrelated behaviours using inheritance

i. Define a class Logger with a method log(self, message) that prints: LOG: {message}.

ii. Define a class Printer with a method display(self, message) that prints: DISPLAY: {message}.

iii. Create a class UtilityTool that uses multiple inheritance to inherit from both Logger and Printer.

iv. Create an instance of UtilityTool and call both the log() and display() methods.

Question 2

Modelling a complex relationship using inheritance. Model a FlyingCar by combining the main functionalities of a
Car and an Airplane.

1. Define a class Car with an __init__ method that sets a max_speed attribute (e.g., 100) and a method drive()
that prints: "Driving on road at {max_speed} km/h."

2. Define a class Airplane with a fly() method that prints: "Flying high in the sky."

3. Create a class AmphibiousVehicle that inherits from both Car and Airplane.

4. Instantiate AmphibiousVehicle and call both drive() and fly().

Method Resolution Order (MRO)

The Method Resolution Order (MRO) is Python’s mechanism for determining the order in which base classes are
searched when looking for a method or attribute. Python uses the C3 linearization algorithm to create a consistent and
predictable MRO.

How MRO Works

• When a method is called on an instance, Python looks for it in the instance’s class and then traverses the MRO
to find it in parent classes.

• The MRO ensures a linear, deterministic order that respects the inheritance hierarchy.

Viewing the MRO

You can inspect a class’s MRO using:

• The __mro__ attribute: Returns a tuple of classes in the resolution order.

• The mro() method: Returns a list of classes.

• The help() function: Displays the MRO along with class documentation.

Example

class A:
def method(self):
print("Method from A")

class B(A):
def method(self):
print("Method from B")
class C(A):
def method(self):
print("Method from C")

class D(B, C):


pass

# Check MRO
print(D.__mro__) # Output: (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class
'__main__.A'>, <class 'object'>)

# Test method resolution


d = D()
[Link]() # Output: Method from B

Explanation of MRO in the Example

• Python first looks in D. If the method isn’t found, it checks B (where it finds method and stops).

• If B didn’t define method, it would check C, then A, and finally object.

• The C3 algorithm ensures that:

▪ B is checked before C (as listed in D’s definition: class D(B, C)).

▪ A is checked after B and C because it’s their parent.

▪ The base class object is always last.

Importance of MRO

• Avoids Ambiguity: Ensures a predictable order for method resolution.

• Supports Cooperative Inheritance: Allows parent classes to call each other’s methods using super().

• Prevents Diamond Problem: The C3 algorithm resolves issues in complex hierarchies where a class inherits
from multiple parents that share a common base (the "diamond problem").

Class work

Question 1

1. Define the classes below.

2. Without running the code, predict what the final output of z.get_id() will be.

3. Run the code to verify your prediction.

class X:
def get_id(self):
return "ID from X"

class Y(X):
pass # No method definition here
class Z(Y):
def get_id(self):
return "ID from Z"

z = Z()
print(z.get_id())

Question two

1. Define the classes below.

2. Predict the output of f.get_source() based on the defined MRO.

3. Run the code and explain in a comment why that particular method was executed.

class E:
def get_source(self):
return "Source E"
class F(E):
def get_source(self):
return "Source F"

class G(E):
def get_source(self):
return "Source G"

class H(G, F): # Note the order: G then F


pass
h = H()
print(h.get_source())

Question three
1. Define the classes below.
2. What is the MRO for class L? Write it as a comment above the code.
3. Predict the output of [Link]().

class I:
def action(self):
print("Action from I")
class J(I):
pass
class K(I):
pass
class L(J, K):
pass
l = L()
[Link]()
Question 4

1. Define the classes below.

2. Use the .__mro__ attribute to print the MRO tuple for class P.

3. Based on the printed MRO, if you call [Link](), which class's version of the method would be executed?

class M:
def data(self):
return "M Data"
class N(M):
def data(self):
return "N Data"

class O(M):
pass

class P(N, O):


pass

# Print the MRO below:

The Diamond Problem

The diamond problem occurs when a class inherits from two classes that share a common ancestor, potentially causing
ambiguity in method resolution.

class A:
def method(self):
print("Method from A")
class B(A):
pass
class C(A):
def method(self):
print("Method from C")
class D(B, C):
pass
d = D()
[Link]() # Output: Method from C
print(D.__mro__) # (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>,
<class 'object'>)

In this case, C’s method is called because C appears before A in the MRO. The C3 algorithm ensures a consistent
resolution.
Using super() in Multiple Inheritance

The super() function is used to call methods from parent classes, and it respects the MRO. In multiple inheritance,
super() ensures that all parent classes in the MRO are called in the correct order, enabling cooperative inheritance.

Example of Cooperative Inheritance

class A:
def method(self):
print("Method from A")
return "A"
class B(A):
def method(self):
print("Method from B")
return super().method() + ", B"
class C(A):
def method(self):
print("Method from C")
return super().method() + ", C"
class D(B, C):
def method(self):
print("Method from D")
return super().method() + ", D"
d = D()
print([Link]()) # Output:
# Method from D
# Method from B
# Method from C
# Method from A
# A, C, B, D

Explanation

• [Link]() calls [Link]() (via super()), which calls [Link](), which calls [Link]().

• Each class adds its contribution to the result, demonstrating cooperative behavior.

• The MRO (D -> B -> C -> A -> object) dictates the order of calls.

Best Practices with super()

• Ensure all classes in the hierarchy use super() consistently to avoid skipping classes in the MRO.

• Avoid hardcoding parent class names (e.g., [Link](self)) to maintain flexibility.

• Be cautious with __init__ methods, as they may need explicit arguments to pass through the MRO.
Classwork

Question one

Demonstrate cooperative inheritance using simple print statements. Observe the exact order in which the methods are
executed.

Instructions:

1. In Class B, Class C, and Class D, call the super().display() method to continue the chain.

2. Run the code and record the order of the printed messages.

class Base:
def display(self):
print("Base: I am the foundation.")
class B(Base):
def display(self):
print("B: I start a branch.")
# TODO: Call super() to move to the next class in the MRO
pass

class C(Base):
def display(self):
print("C: I am the other branch.")
# TODO: Call super() to move to the next class in the MRO
pass

class D(B, C):


def display(self):
print("D: I start the whole process.")
# TODO: Call super() to initiate the MRO chain
pass

# Test
instance = D()
[Link]()
print([Link]()) # Optional: View the MRO list

Expected Output Order (Print Statements):


1. D: I start the whole process.
2. B: I start a branch.
3. [Which Class prints next, according to MRO?]
4. [Which Class prints last?]

Question two
Now let’s Use super() to cooperatively build a single numerical result. Each class must add a specific value to the
result returned by the parent.
Instructions:
1. Implement the add_value() method in all derived classes.
2. Each method should call super().add_value() and add its specified unique value to the result.
3. Base should return the initial value passed to it.

class Base:
def add_value(self, start_num):
# Base class returns the starting number
return start_num

class Adder(Base):
# Adds 5
def add_value(self, start_num):
# TODO: Get the result from super() and add 5
pass

class Multiplier(Base):
# Adds 10
def add_value(self, start_num):
# TODO: Get the result from super() and add 10
pass

class FinalCalc(Adder, Multiplier):


def add_value(self, start_num=100):
print(f"Starting value: {start_num}")
# TODO: Start the chain
pass

# Test
calc = FinalCalc()
print(f"Final Total: {calc.add_value()}")

Expected Output (Final Total):


The final total should be the starting value (100) plus 5, plus 10. (What is the final number?)
Question three
Let’s demonstrate how the MRO controls the order of execution by having each class prepend its name to a growing
string.
Instructions:
1. Implement the process() method in B, C, and D.
2. Each method must call super().process() and prepend its class name to the string returned by super().
class Base:
def process(self):
# Base method returns the final anchor string
return "COMPLETE"

class B(Base):
def process(self):
# TODO: Prepend "B"
pass

class C(Base):
def process(self):
# TODO: Prepend "C"
pass

class D(C, B): # Note the MRO order: D -> C -> B -> Base
def process(self):
# TODO: Prepend "D" and start the chain
pass

# Test
result = D().process()
print(result)
Expected Output:
The output string should clearly show the full MRO path followed by the methods. (Which class name appears first?)

Question four
Let’s see how we can make all __init__ methods in a linear hierarchy to be executed by calling super().__init__.
Instructions:
1. In Class Middle and Class Top, implement the __init__ method.
2. Have each __init__ print a message before calling super().__init__() to show the sequence of calls.

class Base:
def __init__(self):
print("1. Base component initialized.")

class Middle(Base):
def __init__(self):
print("2. Middle component setup.")
# TODO: Call super() to ensure the Base __init__ runs
pass

class Top(Middle):
def __init__(self):
print("3. Top component started.")
# TODO: Call super() to ensure the Middle __init__ runs
pass

# Test
t = Top()
Expected Output Order (Print Statements):
The messages should print from Top down to Base. (Which message is printed last?)

You might also like