1

I'm aware of Python name mangling, but am running into unexpected behavahior while using mutliple inheritence. Take for example:

class A(object):
  def __init__(self):
    self.__foo=1
  def bar(self):
    return self.__foo

class B(object):
  pass

class C(B):
  def __init__(self):
    self.test=1

class D(C,A):
  pass

print D().bar()

which gives the error:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in bar
AttributeError: 'D' object has no attribute '_A__foo'

However, if I change the definition of "D" to:

class D(A,C):
  pass

it works. Output:

1 

Additionally, if I remove all the member variables from class "C", either definition of D work

class C(B):
  pass

class D1(A, C):
  pass

class D2(C, A):
  pass

print D1().bar()
print D2().bar()

Output:

1
1

Can anyone enlighten me as to what is going on here? I would expect any of these class definitions to behave the same.

Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
joesdiner
  • 1,125
  • 9
  • 11

1 Answers1

3

In your original definition, class D(C, A), the call to D.__init__ resolved to C.__init__, so A.__init__ was never called and the __foo attribute was never created.

In the modified definition (class D(A, C)), the call to D.__init__ resolved to A.__init__, which did set __foo.

It's important to call parent constructors to ensure that your instances are properly initialized, and unlike Java, parent constructors have to be called explicitly.

For multiple inheritance, there are fewer problems if you use super to ensure every class's __init__ method gets called.

class A(object):
    def __init__(self):
        super(A, self).__init__()
        self.__foo = 1

    def bar(self):
        return self.__foo


class B(object):
    pass


class C(B):
    def __init__(self):
        super(C, self).__init__()
        self.test = 1


class D1(C,A):
    pass


class D2(A,C):
    pass


# Both will print 1
print D1().bar()
print D2().bar()

As long as all classes use super properly, each method will be called once, whether the method resolution order is D1's [C, B, A, object] or D2's [A, C, B, object].

chepner
  • 497,756
  • 71
  • 530
  • 681
  • Thanks. That was it. Now in my real code I just need to figure out how to use __init__s that take different number of parameters with multiple inheritance... – joesdiner Feb 13 '19 at 18:50
  • A few general rules: every class has to accept arbitrary keyword arguments in addition to any named parameters, and those need be passed to the call of `super().__init__`. Further, there has to be a "root" class for each additional parameter that is responsible for removing that parameter and not passing it to the next call. This ensures that all additional parameters eventually reach the class that need them, and that no parameters are passed to `object.__init__`. – chepner Feb 13 '19 at 18:58