python multi inheritance with parent classes have different __init__()
Question: Here both `B` and `C` are derived from `A`, but with different `__init__()`
parameters. My question is how to write the correct/elegant code here to
initialize self.a,self.b,self.c1,self.c2 in the following example? Maybe
another question is--is it a good coding practice to do this variable setting
in `__init()__` function or it is better to use simpler `__init__()` function,
and do `set()` function for each class later, which seems not as simple as to
just do it in `__init()__`?

    
    
    class A(object):
        __init__(self,a):
            self.a=a
    class B(A):
        __init__(self,a,b):
            super(B,self).__init__(a)
            self.b=b
    class C(A):
        __init__(self,a,c1,c2):
            super(C,self).__init__(a)
            self.c1=c1
            self.c2=c2
    
    class D(B,C)
        __init__(self,a,b,c1,c2,d):
            #how to write the correct/elegant code here to initialize self.a,self.b,self.c1,self.c2?
            #can I use super(D,self) something?
            self.d=d
            self.dd=self.a+self.b+2*self.c1+5*self.c2+3*self.d
    
    d=D(1,2,3,4,5)   
    


Answer: Multiple inheritance in Python requires that all the classes cooperate to make
it work. In this case, you can make them cooperate by having the `__init__`
method in each class accept arbitrary `**kwargs` and pass them on when they
call `super().__init__`.

For your example class hierarchy, you could do something like this:

    
    
    class A(object):
        __init__(self,a):  # Don't accept **kwargs here! Any extra arguments are an error!
            self.a=a
    
    class B(A):
        __init__(self, b, **kwargs):  # only name the arg we care about (the rest go in **kwargs)
            super(B, self).__init__(**kwargs)  # pass on the other keyword args
            self.b=b
    
    class C(A):
        __init__(self, c1, c2, **kwargs):
            super(C,self).__init__(**kwargs)
            self.c1=c1
            self.c2=c2
    
    class D(B,C)
        __init__(self, d, **kwargs):
            super(D,self).__init__(**kwargs)
            self.d=d
            self.dd=self.a+self.b+2*self.c1+5*self.c2+3*self.d
    

Note that if you wanted `D` to use the argument values directly (rather than
using `self.a`, etc.), you could both take them as named arguments and still
pass them on in the `super()` call:

    
    
    class D(B,C)
        __init__(self,a, b, c1, c2, d, **kwargs): # **kwargs in case there's further inheritance
            super(D,self).__init__(a=a, b=b, c1=c1, c2=c2, **kwargs)
            self.d = d
            self.dd = a + b + 2 * c1 + 5 * c2 + 3 * d   # no `self` needed in this expression!
    

Accepting and passing on some args is important if some of the parent classes
don't save the arguments (in their original form) as attributes, but you need
those values. You can also use this style of code to pass on modified values
for some of the arguments (e.g. with `super(D, self).__init__(a=a, b=b,
c1=2*c1, c2=5*c2, **kwargs)`).

This kind of collaborative multiple inheritance with varying arguments is
almost impossible to make work using positional arguments. With keyword
arguments though, the order of the names and values in a call doesn't matter,
so it's easy to pass on named arguments and `**kwargs` at the same time
without anything breaking. Using `*args` doesn't work as well (though recent
versions of Python 3 are more flexible about how you can call functions with
`*args`, such as allowing multiple unpackings in a single call: `f(*foo, bar,
*baz)`).

If you were using Python 3 (I'm assuming not, since you're explicitly passing
arguments to `super`), you could make the arguments to your collaborative
functions "keyword-only", which would prevent users from getting very mixed up
and trying to call your methods with positional arguments. Just put a bare `*`
in the argument list before the other named arguments: `def __init__(self, *,
c1, c2, **kwargs):`.

