
Multiple Inheritance and Method Resolution Order (MRO) in Python
Understanding multiple inheritance and the Method Resolution Order (MRO) is essential for grasping how classes behave in Python when a class inherits from multiple base classes.
- Multiple Inheritance
Python supports multiple inheritance, meaning a class can inherit from more than one base class. - Method Resolution Order (MRO)
The MRO defines the order in which attributes and methods are looked up. The MRO can be accessed via the `__mro__` attribute:
In other words, multiple inheritance and the MRO enable Python to consistently determine which method to call when multiple base classes have methods with the same name.
Let's clarify these concepts with some practical examples.
Consider the following example:
class A:
a = 'alpha'
class B:
b = 'beta'
class C:
c = 'gamma'
class D(A, B, C):
pass
In this code, class `D` inherits attributes from classes `A`, `B`, and `C`.
Create an instance of class D:
d = D()
Now, if you query the attributes `a`, `b`, and `c` from the instance, it will return the values of the attributes from the original classes.
print(d.a, d.b, d.c)
alpha beta gamma
The `__base__` and `__bases__` Attributes
These two attributes let you see which base classes a class inherits its methods and attributes from.
The `__base__` attribute returns the first base class in the multiple inheritance order.
For class D, the first base class is class A.
print(D.__base__)
<class '__main__.A'>
The `__bases__` attribute, on the other hand, returns all the base classes of a class.
For example, if you query it on class D, it returns a tuple containing the base classes A, B, and C from which it inherits.
print(D.__bases__)
(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>)
The super() Function in Multiple Inheritance
The super() function allows you to create an instance that delegates method calls to the next class in the MRO.
Consider the following example:
class A:
def foo(self):
print('Hello')
class B:
def foo(self):
print('Hi')
class C:
def foo(self):
print('Hey')
class D(A, B, C):
def foo(self):
super().foo()
In this case, class D calls the `foo` method of the first base class A in the MRO.
For example, create an instance of class D:
d = D()
Now, if you call the d.foo() method from the instance, it will output "Hello", which is the result of the `foo()` method from class A.
d.foo()
Hello
This happens because the first base class in the MRO of class D is class A.
Method Resolution Order (MRO)
The Method Resolution Order (MRO) defines the order in which Python looks up attributes and methods in base classes.
You can check it via the `__mro__` attribute.
For example, define these classes:
class A:
pass
class B:
def foo(self):
print('Hi')
class C:
def foo(self):
print('Hey')
class D(A, B, C):
pass
Class D inherits attributes and methods from base classes A, B, and C in this exact order.
Now create an object of class D:
d = D()
If you query the `__mro__` attribute, it will return the method resolution order of class D.
print(D.__mro__)
(<class '__main__.D'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class 'object'>)
This result shows that class D resolves methods by first looking in itself, then in class A, class B, and finally in class C.
When you call the `d.foo()` method, Python follows the MRO.
d.foo()
Hi
In this case, Python does not find the `foo()` method in class D, so it looks in the first base class in the MRO, which is class A.
Since it does not find the `foo()` method in class A either, it then looks in class B and executes it.
If the `foo` method had not been found in any of these classes, Python would have raised an AttributeError.