Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling parent class __init__ with multiple inheritance, what's the right way?

Say I have a multiple inheritance scenario:

class A(object):     # code for A here  class B(object):     # code for B here  class C(A, B):     def __init__(self):         # What's the right code to write here to ensure          # A.__init__ and B.__init__ get called? 

There's two typical approaches to writing C's __init__:

  1. (old-style) ParentClass.__init__(self)
  2. (newer-style) super(DerivedClass, self).__init__()

However, in either case, if the parent classes (A and B) don't follow the same convention, then the code will not work correctly (some may be missed, or get called multiple times).

So what's the correct way again? It's easy to say "just be consistent, follow one or the other", but if A or B are from a 3rd party library, what then? Is there an approach that can ensure that all parent class constructors get called (and in the correct order, and only once)?

Edit: to see what I mean, if I do:

class A(object):     def __init__(self):         print("Entering A")         super(A, self).__init__()         print("Leaving A")  class B(object):     def __init__(self):         print("Entering B")         super(B, self).__init__()         print("Leaving B")  class C(A, B):     def __init__(self):         print("Entering C")         A.__init__(self)         B.__init__(self)         print("Leaving C") 

Then I get:

Entering C Entering A Entering B Leaving B Leaving A Entering B Leaving B Leaving C 

Note that B's init gets called twice. If I do:

class A(object):     def __init__(self):         print("Entering A")         print("Leaving A")  class B(object):     def __init__(self):         print("Entering B")         super(B, self).__init__()         print("Leaving B")  class C(A, B):     def __init__(self):         print("Entering C")         super(C, self).__init__()         print("Leaving C") 

Then I get:

Entering C Entering A Leaving A Leaving C 

Note that B's init never gets called. So it seems that unless I know/control the init's of the classes I inherit from (A and B) I cannot make a safe choice for the class I'm writing (C).

like image 647
Adam Parkin Avatar asked Mar 05 '12 23:03

Adam Parkin


People also ask

Why doesn't my code work correctly with multiple inheritance?

If your base classes are separate entities that are capable of functioning independently and they don't know each other, they're not designed for multiple inheritance. Example: Important: Notice that neither Foo nor Bar calls super ().__init__ ()! This is why your code didn't work correctly.

How does the subclass inherit from the mixin first?

The subclass inherits from the mixin first: class FooBar (FooMixin, Bar). If the order of the base classes is wrong, the mixin's constructor will never be called. All base classes are designed for cooperative inheritance. Classes designed for cooperative inheritance are a lot like mixins: They pass through all unused arguments to the next class.

How to create a class that inherits from an object?

If your class inherits directly from object, make sure to add an empty constructor like so: Anyway, in this situation, you will have to call each parent constructor manually. There are two ways to do this: Each of these two methods has its own advantages and disadvantages. If you use super, your class will support dependency injection.

How to check if a class is designed for multiple inheritance?

The constructor is part of a class's public interface. If the class is designed as a mixin or for cooperative inheritance, that must be documented. If the docs don't mention anything of the sort, it's safe to assume that the class isn't designed for cooperative multiple inheritance.


2 Answers

The answer to your question depends on one very important aspect: Are your base classes designed for multiple inheritance?

There are 3 different scenarios:

  1. The base classes are unrelated, standalone classes.

    If your base classes are separate entities that are capable of functioning independently and they don't know each other, they're not designed for multiple inheritance. Example:

    class Foo:     def __init__(self):         self.foo = 'foo'  class Bar:     def __init__(self, bar):         self.bar = bar 

    Important: Notice that neither Foo nor Bar calls super().__init__()! This is why your code didn't work correctly. Because of the way diamond inheritance works in python, classes whose base class is object should not call super().__init__(). As you've noticed, doing so would break multiple inheritance because you end up calling another class's __init__ rather than object.__init__(). (Disclaimer: Avoiding super().__init__() in object-subclasses is my personal recommendation and by no means an agreed-upon consensus in the python community. Some people prefer to use super in every class, arguing that you can always write an adapter if the class doesn't behave as you expect.)

    This also means that you should never write a class that inherits from object and doesn't have an __init__ method. Not defining a __init__ method at all has the same effect as calling super().__init__(). If your class inherits directly from object, make sure to add an empty constructor like so:

    class Base(object):     def __init__(self):         pass 

    Anyway, in this situation, you will have to call each parent constructor manually. There are two ways to do this:

    • Without super

      class FooBar(Foo, Bar):     def __init__(self, bar='bar'):         Foo.__init__(self)  # explicit calls without super         Bar.__init__(self, bar) 
    • With super

      class FooBar(Foo, Bar):     def __init__(self, bar='bar'):         super().__init__()  # this calls all constructors up to Foo         super(Foo, self).__init__(bar)  # this calls all constructors after Foo up                                         # to Bar 

    Each of these two methods has its own advantages and disadvantages. If you use super, your class will support dependency injection. On the other hand, it's easier to make mistakes. For example if you change the order of Foo and Bar (like class FooBar(Bar, Foo)), you'd have to update the super calls to match. Without super you don't have to worry about this, and the code is much more readable.

  2. One of the classes is a mixin.

    A mixin is a class that's designed to be used with multiple inheritance. This means we don't have to call both parent constructors manually, because the mixin will automatically call the 2nd constructor for us. Since we only have to call a single constructor this time, we can do so with super to avoid having to hard-code the parent class's name.

    Example:

    class FooMixin:     def __init__(self, *args, **kwargs):         super().__init__(*args, **kwargs)  # forwards all unused arguments         self.foo = 'foo'  class Bar:     def __init__(self, bar):         self.bar = bar  class FooBar(FooMixin, Bar):     def __init__(self, bar='bar'):         super().__init__(bar)  # a single call is enough to invoke                                # all parent constructors          # NOTE: `FooMixin.__init__(self, bar)` would also work, but isn't         # recommended because we don't want to hard-code the parent class. 

    The important details here are:

    • The mixin calls super().__init__() and passes through any arguments it receives.
    • The subclass inherits from the mixin first: class FooBar(FooMixin, Bar). If the order of the base classes is wrong, the mixin's constructor will never be called.
  3. All base classes are designed for cooperative inheritance.

    Classes designed for cooperative inheritance are a lot like mixins: They pass through all unused arguments to the next class. Like before, we just have to call super().__init__() and all parent constructors will be chain-called.

    Example:

    class CoopFoo:     def __init__(self, **kwargs):         super().__init__(**kwargs)  # forwards all unused arguments         self.foo = 'foo'  class CoopBar:     def __init__(self, bar, **kwargs):         super().__init__(**kwargs)  # forwards all unused arguments         self.bar = bar  class CoopFooBar(CoopFoo, CoopBar):     def __init__(self, bar='bar'):         super().__init__(bar=bar)  # pass all arguments on as keyword                                    # arguments to avoid problems with                                    # positional arguments and the order                                    # of the parent classes 

    In this case, the order of the parent classes doesn't matter. We might as well inherit from CoopBar first, and the code would still work the same. But that's only true because all arguments are passed as keyword arguments. Using positional arguments would make it easy to get the order of the arguments wrong, so it's customary for cooperative classes to accept only keyword arguments.

    This is also an exception to the rule I mentioned earlier: Both CoopFoo and CoopBar inherit from object, but they still call super().__init__(). If they didn't, there would be no cooperative inheritance.

Bottom line: The correct implementation depends on the classes you're inheriting from.

The constructor is part of a class's public interface. If the class is designed as a mixin or for cooperative inheritance, that must be documented. If the docs don't mention anything of the sort, it's safe to assume that the class isn't designed for cooperative multiple inheritance.

like image 51
Aran-Fey Avatar answered Oct 16 '22 08:10

Aran-Fey


Both ways work fine. The approach using super() leads to greater flexibility for subclasses.

In the direct call approach, C.__init__ can call both A.__init__ and B.__init__.

When using super(), the classes need to be designed for cooperative multiple inheritance where C calls super, which invokes A's code which will also call super which invokes B's code. See http://rhettinger.wordpress.com/2011/05/26/super-considered-super for more detail on what can be done with super.

[Response question as later edited]

So it seems that unless I know/control the init's of the classes I inherit from (A and B) I cannot make a safe choice for the class I'm writing (C).

The referenced article shows how to handle this situation by adding a wrapper class around A and B. There is a worked-out example in the section titled "How to Incorporate a Non-cooperative Class".

One might wish that multiple inheritance were easier, letting you effortlessly compose Car and Airplane classes to get a FlyingCar, but the reality is that separately designed components often need adapters or wrappers before fitting together as seamlessly as we would like :-)

One other thought: if you're unhappy with composing functionality using multiple inheritance, you can use composition for complete control over which methods get called on which occasions.

like image 31
Raymond Hettinger Avatar answered Oct 16 '22 09:10

Raymond Hettinger