Multiple inheritance in Python: how MRO (Method Resolution Order) decides which constructor to use

Video thumbnail

In this article, we'll explain how we can invoke class constructor methods when we have multiple inheritance in Python.

We're working with a system of three main nested classes:

View → The layer consumed by the end user.

Intermediate → Decides whether to call PayPal or Stripe.

Payment Gateway → In this case, PayPal.

class PaymentPaypalClient(AbstractPayment):
    def __init__(self):
        super().__init__()
        ***
        
class BasePayment(PaymentPaypalClient, PaymentStripeClient):
    def __init__(self):
        super().__init__()
        
class PaymentBookView(LoginRequiredMixin, View, BasePayment):
    def __init__(self):
        # super().__init__()
        BasePayment.__init__(self)      

In Django, the view plays the role of the controller (similar to frameworks like CodeIgniter, Laravel, or others).

Multiple Inheritance in Python

The beauty of Python is that we have multiple inheritance, without the problems of PHP (traits), Dart (mixins), or Kotlin (mixins). Just add the class, a comma, and get on with life.

The problem arises when we need to initialize constructor methods (__init__).

In this case, PayPal needs to initialize its constructor because there we configure the environment, keys, and base URL (depending on whether we're in development or production):

class PaymentPaypalClient(AbstractPayment):
    
    def __init__(self):

        self.base_url = (
            "https://api-m.paypal.com"
            if env
            else "https://api-m.sandbox.paypal.com"
        )
        self.client_id = settings.PAYPAL_CLIENT_ID
        self.secret = settings.PAYPAL_SECRET

The problem is that we weren't calling the constructors for these classes. This causes the PayPal order to fail when processing it with errors such as base_url not defined.

This error is a classic of multiple inheritance in Python and has to do with MRO (Method Resolution Order), that is, the order in which Python resolves the inheritance chain.

Explanation of the MRO

In short:

  • Python takes the first class listed in the inheritance.
  • If it doesn't find a constructor, it moves on to the next one.
  • If it finds one, it executes it and stops, unless that constructor explicitly calls super().
  • In this case, the view had a constructor, but it didn't call the superclass, which broke the chain. That's why it never reached the BasePayment or PayPal constructors.

In the case of:

class PaymentBookView(LoginRequiredMixin, View, BasePayment):
    def __init__(self):
        # super().__init__()
        BasePayment.__init__(self)       

It only calls the first constructor, which would be the one for the View class since the LoginRequiredMixin mixin does not have a constructor:

class LoginRequiredMixin(AccessMixin):
    """Verify that the current user is authenticated."""

    def dispatch(self, request, *args, **kwargs):
        if not request.user.is_authenticated:
            return self.handle_no_permission()
        return super().dispatch(request, *args, **kwargs)
        
class View:
   ***
    def __init__(self, **kwargs):
        """
        Constructor. Called in the URLconf; can contain helpful extra
        keyword arguments, and other things.
        """

And the constructor for the BasePayment class, and thus the constructor for the PaymentPaypalClient class, is never called, and the base URL and PayPal key parameters are not initialized. To avoid this, we have two ways, either by arranging the order of the inherited classes:

There are two ways to solve this:

  1. Reorder classes in inheritance → For example, put BasePayment before the view: class PaymentBookView(LoginRequiredMixin, BasePayment, View)
  2. Directly call the constructor of the required class → BasePayment.__init__(self, ...):
class PaymentBookView(LoginRequiredMixin, View, BasePayment):
   def __init__(self):
       BasePayment.__init__(self) 

This way we ensure that the constructors actually execute in the expected order.

Conclusion

The problem was not correctly calling the superclass in a multiple inheritance context.

The MRO defines the order of execution, but if one class breaks the chain, the other constructors are never executed.

The solution is simple: explicitly call the correct constructor or reorder the inheritance as needed.

In short: if you want to avoid these types of errors in Python, make sure you understand how the MRO works and how constructors are executed in multiple inheritance.

If you still don't understand, you can do some debugging by adding print to the constructors, and commenting or uncommenting the constructors in the parts of the code where you have doubts about how they are executed:

class PaymentPaypalClient(AbstractPayment):
    def __init__(self):
        print('THREE')
        super().__init__()
        
class BasePayment(PaymentPaypalClient, PaymentStripeClient):
    def __init__(self):
        print('TWO')
        super().__init__()
        
class PaymentBookView(LoginRequiredMixin, View, BasePayment):
    def __init__(self):
        print('ONE')
        BasePayment.__init__(self)           

I agree to receive announcements of interest about this Blog.

The MRO (Method Resolution Order) in Python defines how constructors are executed in multiple inheritance. If a class doesn't call super(), it breaks the chain, and other constructors aren't executed.

| 👤 Andrés Cruz

🇪🇸 En español