Inheritance vs. Composition in Python for this specific case?

Question:

Assume I have a series of classes as follows:

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

If now D is a both B and C, it can be implemented as

class D(B,C)
    __init__(self,a,b,c1,c2,d):
        pass

Or D can be understood as a combination of B and C

class D(B,C)
    __init__(self,a,b,c1,c2,d):
        self.b=B(a,b)
        self.c=C(a,c)

which seems a bit more complicated than the multi-inheritance case.

Well, Now let’s imaging class D2 is a combination of 5 pieces of B and 3 pieces of C, and class D3 is made of D2 plus an additional C(with different parameter than the ones in D), and D4 is made of D2 and 2 additional pieces of C(these 2 additional C has same parameter but different from the ones in D2). It seems D2 is good to use composition, but D3 is good to use inheritance of D2 and C, and D4 is good to use inheritance of D2 and C.

Use combinations, to me, has the disadvantage that I have to write d.c.c1 rather than d.c1(as in inheritance case)to get the parameter c or I need to store the parameter c in D directly(besides the one as d.c.c1). Is there any philosophical consistent way to deal with these classes? Thanks!

Asked By: george andrew

||

Answers:

You’re bound to get some level of opinion on a question like this — My answer is to use inheritance iff your classes are designed to be inherited. Use composition in all the other cases. In practice, this usually means that if the code that you are extending exists in a realm out of your control, don’t subclass it unless their documentation talks about how you can subclass it effectively.

In this case, since it seems like you are the author of the class hierarchy, inheritance seems to make sense (though some of your assertions are incorrect — D is not so easy to implement as you have supposed since none of the superclass __init__ will get called). You probably should familiarize yourself with the design pattern in Raymond Hettinger’s "super considered super" article

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

class B(A):
    def __init__(self, b, **kwargs):
        super(B, self).__init__(**kwargs)
        self.b = b

class C(A):
    def __init__(self, c1, c2, **kwargs):
        super(C, self).__init__(**kwargs)
        self.c1 = c1
        self.c2 = c2

now the implementation of D is trivially simple:

class D(B, C):
    pass

Note that we don’t even need to define __init__ because all of the constructors of the super classes can accept arbitrary keyword arguments. Instantiating a D looks like:

d = D(a='a', b='b', c1='c1', c2='c2')
Answered By: mgilson
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.