3

Edit: I am on Python 3.

I have a class X that extends both Y and Z:

class X(Y, Z):
    def __init__(self):
    # Call Y's __init__
    # Call Z's __init
    pass

I need to call __init__ on both Y and Z from the __init__ on X. What would be the best way to do this?

vmonteco
  • 14,136
  • 15
  • 55
  • 86
masnun
  • 11,635
  • 4
  • 39
  • 50

2 Answers2

6

This depends entirely on whether Y and Z were designed to work cooperatively with multiple inheritance. If they are, then you should be able to just use super:

class X(Y, Z):
    def __init__(self):
        super().__init__()

When I say that Y and Z need to be designed to work cooperatively with multiple inheritance, I mean that they must also call super in their __init__ methods:

class Y:
    def __init__(self):
        print('Y.__init__')
        super().__init__()

class Z:
    def __init__(self):
        print('Z.__init__')
        super().__init__()

They should also have some strategy for handling different arguments since (particularly the constructor) of a class frequently differ in which keyword arguments that they accept. At this point, it's worth reading Super considered super! and Super considered harmful to understand some common idioms and pitfalls regarding super.

If you don't know whether Y and Z were designed for cooperative multiple inheritance, then the safest recourse is to assume that they weren't.


If the classes aren't designed to work with cooperative multiple inheritance, then you need to start stepping lightly (you're in dangerous waters). You can call each __init__ individually:

class X(Y, Z):
    def __init__(self):
        Y.__init__(self)
        Z.__init__(self)

But really it's probably better to not use multiple inheritance and instead choose a different paradigm (e.g. composition).

mgilson
  • 300,191
  • 65
  • 633
  • 696
  • In my case, the parents are - `threading.Thread` and `logging.Handler` - how do I check if they are designed to work cooperatively in multiple inheritance? – masnun Sep 09 '16 at 15:40
  • 1
    As far as I can tell, [`threading.Thread`](https://hg.python.org/cpython/file/tip/Lib/threading.py#l738) does not. – mgilson Sep 09 '16 at 15:42
  • @masnun The question you need to ask yourself is: why do you want to inherit from both of them? You are mixing unrelated concepts. It would be cleaner to use a class with both of them as fields (or some other clean pattern). – freakish Sep 09 '16 at 15:45
  • @mgilson I guess `Handler` also doesn't use `super`. I can see it calls `Filterer.__init__(self)` in it's own `__init__`. Thanks for the explanation. – masnun Sep 09 '16 at 15:48
  • @freakish You have an excellent point. I am going to try and separate the concerns in two classes and glue them together. In case that doesn't work, I will have to depend on multiple inheritance. – masnun Sep 09 '16 at 15:50
  • @masnun I can assure it will work. Plus you won't have to deal with weird issues around multiple inheritance. This should be avoided as much as possible. – freakish Sep 09 '16 at 15:51
  • @mgilson, could you (or anyone else) explain why the last solution (i.e. calling `Y.__init__()` directly rather than using `super()`) is dangerous? – mloning Mar 10 '20 at 09:39
  • 1
    Let's assume that `Y` also has a base-class (`Q`) and `Y` calls `super().__init__()` to make sure that it (implicitly) calls `Q.__init__()`. Depending on the method resolution order, `Y`'s `super().__init__()` might actually call `Z.__init__` instead of `Q.__init__`. If `Z.__init__` also `super`s, then you'll call `Z.__init__` twice (once here and once explicitly later). If `Z.__init__` doesn't `super`, then you'll likely skip `Q.__init__` entirely. – mgilson Mar 12 '20 at 05:27
4

Generally there is no other way then to call Y.__init__(self) and Z.__init__(self) directly. It's the only way to ensure that both constructors are called at least once.

However in some weird (or normal, depending on how you look at it) cases this can lead to very unintuitive results where base constructors are called multiple times:

class Y:
    def __init__(self):
        print('Y')
        super().__init__()

class Z:
    def __init__(self):
        print('Z')
        super().__init__()

class X(Y, Z):
    def __init__(self):
        print('X')
        Y.__init__(self)
        Z.__init__(self)

X()

Results in

X
Y
Z
Z

So in that case super().__init__() in X (instead of both .__init__() calls) seems natural and will work correctly. However it will fail if Y and Z don't call super().__init__() as well. In that case you will only call one of the constructors.

So generally there is no way to solve your issue correctly without knowing implementation of base classes.

YARtAMI = Yet Another Reason to Avoid Multiple Inheritance

freakish
  • 54,167
  • 9
  • 132
  • 169
  • Thanks for the detailed explanation. In my case none of the parent classes have any common ancestor. – masnun Sep 09 '16 at 15:51
  • @masnun They don't have to have. I've updated the answer. – freakish Sep 09 '16 at 15:52
  • Is there any explanation for understanding why the first call `super().__init__()` in Y triggers the Z.__init__()??? I understood Y and Z inherit from `object`, so I can't understand why the first 'Z' print on screen – madtyn Oct 15 '17 at 02:27