Passing **kwargs arguments to parent classes in multiple inheritance loses kwargs content

Question:

In a multiple inheritance scenario, how does Python3 pass the arguments to all the parent classes? Consider the toy program below:

class A(object):
    def __init__(self, a='x', **kwargs):
        print('A', kwargs)
        super().__init__()

class B(object):
    def __init__(self, **kwargs):
        print('B', kwargs)
        super().__init__()

class C(A,B):
    def __init__(self, **kwargs):
        print('C', kwargs)
        super().__init__(**kwargs)

c = C(a='A', b='B', c='C')

The output is:

C {'a': 'A', 'b': 'B', 'c': 'C'}
A {'b': 'B', 'c': 'C'}
B {}

What I expect to do is to pass the same kwargs to all the parent classes and let them use the values as they want. But, what I am seeing is that once I’m down the first parent class A, the kwargs is consumed and nothing is passed to B.

Please help!


Update
If the order of inheritance was deterministic and I knew the exhaustive list of kwargs that can be passed down the inheritance chain, we could solve this problem as

class A(object):
    def __init__(self, a='x', **kwargs):
        print('A', kwargs)
        super().__init__(**kwargs)

class B(object):
    def __init__(self, **kwargs):
        print('B', kwargs)
        super().__init__()

class C(A,B):
    def __init__(self, **kwargs):
        print('C', kwargs)
        super().__init__(**kwargs)

c = C(a='A', b='B', c='C')

Here, since A passes down kwargs and we know that B would be called after it, we are safe, and by the time object.__init__() is invoked, the kwargs would be empty.

However, this may not always be the case.

Consider this variation:

class C(B,A):
    def __init__(self, **kwargs):
        print('C', kwargs)
        super().__init__(**kwargs)

Here, object.__init__() invoked from class A would raise an exception since there are still kwargs left to consume.

So, is there a general design guideline that I should be following?

Asked By: Anshul

||

Answers:

As suggested by @deceze and also pointed out in the discussion shared by @wim in the comments to the question, here’s a summary (verbatim):

if you pass arbitrary arguments to unknown parents, your class must be designed as a mixin, not as an optional stand-alone class or mixin. Typically in a class hierarchy, each class would define concrete parameters and only pass the remainders through as kwargs, or at least ensure that used kwargs are removed before passing remainders through to the parent; then all kwargs should be used up and object will receive zero kwargs, as it should.

You design your classes to be used a certain way. If they’re used incorrectly, it’s fine if that produces an error. You just need to use your classes the way you designed them, or clearly document how they’re supposed to be used if you’re not going to be the user. Then there shouldn’t be much fretting about "maybe another classes uses the argument" or "user instantiates the wrong class".

Summary:
Do not overgeneralize the classes or try to make them too flexible for the just-in-case scenario that someone later would be using them. In such cases, proper documentation for the person expanding on the classes should be in place.

Answered By: Anshul
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.