Correct Parameter Passing with super() in Python Multiple Inheritance

Nov 26, 2025 · Programming · 9 views · 7.8

Keywords: Python | super() method | multiple inheritance | parameter passing | MRO | Base class

Abstract: This article provides an in-depth analysis of parameter passing issues with Python's super() method in multiple inheritance scenarios. It examines the root cause of TypeError when object.__init__() receives parameters and presents a robust solution using a Base class as a parameter absorber. The discussion covers MRO mechanics, complete code examples, and best practices for handling parameters in complex inheritance hierarchies.

Problem Background and Error Analysis

In Python multiple inheritance scenarios, parameter passing when using the super() method for parent class method calls often poses challenges for developers. According to the provided Q&A data, when attempting to run code considered as a "correct example," a TypeError: object.__init__() takes no parameters error occurs.

The fundamental cause of this error lies in Python's object class, which serves as the ultimate base class for all classes and whose __init__ method accepts no parameters. In multiple inheritance chains, when the MRO (Method Resolution Order) ultimately points to object.__init__, if parameters are still being passed, this exception is triggered.

MRO Mechanism and Parameter Passing Principles

Python uses the C3 linearization algorithm to determine method resolution order, ensuring that method calls in multiple inheritance follow a logical sequence. In the example code, class E's MRO is ['E', 'C', 'A', 'D', 'B', 'object'], illustrating the specific path of method calls.

The key issue is that every method using super() must be able to accept and pass *args and **kwargs parameters. However, object.__init__ violates this principle by not accepting any parameters, causing the parameter passing chain to break at the final step.

Base Class Solution

To resolve this issue, the best practice is to introduce a custom Base class as the direct or indirect base class for all other classes:

class Base(object):
    def __init__(self, *args, **kwargs): 
        pass

class A(Base):
    def __init__(self, *args, **kwargs):
        print("A")
        super(A, self).__init__(*args, **kwargs)

class B(Base):
    def __init__(self, *args, **kwargs):
        print("B")
        super(B, self).__init__(*args, **kwargs)

class C(A):
    def __init__(self, arg, *args, **kwargs):
        print("C", "arg=", arg)
        super(C, self).__init__(arg, *args, **kwargs)

class D(B):
    def __init__(self, arg, *args, **kwargs):
        print("D", "arg=", arg)
        super(D, self).__init__(arg, *args, **kwargs)

class E(C, D):
    def __init__(self, arg, *args, **kwargs):
        print("E", "arg=", arg)
        super(E, self).__init__(arg, *args, **kwargs)

print("MRO:", [x.__name__ for x in E.__mro__])
E(10)

The core of this solution lies in the Base class serving as a parameter absorber—it accepts all passed parameters but does nothing with them. By placing Base in the appropriate position in the MRO (typically before object), the integrity of the parameter passing chain is ensured.

MRO Verification and Execution Results

Using the corrected code, the MRO becomes ['E', 'C', 'A', 'D', 'B', 'Base', 'object']. The execution process is as follows:

Best Practices for Parameter Handling

In addition to the Base class solution, other parameter handling strategies are worth considering. One common approach involves using **kwargs with pop operations:

class First(object):
    def __init__(self, *args, **kwargs):
        self.first_arg = kwargs.pop('first_arg')
        super(First, self).__init__(*args, **kwargs)

class Second(First):
    def __init__(self, *args, **kwargs):
        self.second_arg = kwargs.pop('second_arg')
        super(Second, self).__init__(*args, **kwargs)

class Third(Second):
    def __init__(self, *args, **kwargs):
        self.third_arg = kwargs.pop('third_arg')
        super(Third, self).__init__(*args, **kwargs)

# Usage example
third = Third(first_arg=1, second_arg=2, third_arg=3)

This method is suitable when parameter names do not conflict—each class extracts the parameters it needs from kwargs and then passes the remainder along.

Comparison with Parameter Passing in Other Languages

Examining parameter passing mechanisms in C++ reveals design philosophy differences across languages when addressing similar issues. In C++, forwarding references and perfect forwarding enable similar parameter flexibility, but with more complex syntax:

template <typename S>
void add_from_stream(S&& s)
requires std::is_convertible_v<S, std::istream const&>
{
    // Use std::forward<S>(s) for perfect forwarding
}

In contrast, Python's *args and **kwargs mechanisms offer a more concise approach to parameter handling, though care must be taken to maintain parameter passing integrity in multiple inheritance contexts.

Practical Application Recommendations

In practical development, adhere to the following principles:

  1. Ensure that __init__ methods in all classes potentially involved in multiple inheritance accept *args, **kwargs parameters
  2. Use a Base class as a parameter absorber to guarantee parameter passing chain integrity
  3. Consider MRO implications when designing class hierarchies
  4. For classes with specific parameter requirements, use kwargs.pop() to extract needed parameters
  5. Maintain consistent parameter naming to avoid conflicts

By following these best practices, developers can effectively avoid parameter passing issues in Python multiple inheritance, building more robust and maintainable object-oriented code.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.