Multiple inheritance, super() and changing signature

Ben Finney ben+python at benfinney.id.au
Tue May 31 16:01:43 EDT 2016


Nagy László Zsolt <gandalf at shopzeus.com> writes:

> Today I come across this problem for the N+1st time. Here are some
> classes for the example:

Thank you for the example.

(Note that ‘__init__’ is not a constructor, because it operates on the
*already constructed* instance, and does not return anything. Python's
classes implement the constructor as ‘__new__’, and you very rarely need
to bother with that.)


For the reasons you describe (among others), participating in multiple
inheritance is tricky.

As described in numerous articles, for example
<URL:https://rhettinger.wordpress.com/2011/05/26/super-considered-super/>,
the right way to do this is:

* Obey the Liskov Substitution Principle (sub-classes should override a
  method only when they allow all the parent's behaviour as well as
  their own).

  This also means that the sub-class should pass any unknown arguments
  along to the superclass's method to handle.

* Once a parameter is handled in a method, remove it from the set and
  pass only the remaining arguments along to the superclass's method.

> class Observable:
>     """Implements the observer-observable pattern."""
>
>     def __init__(self):
>         # initialization code here...
>         super(Observable, self).__init__()

This should be:

    def __init__(self, **kwargs):
        # …
        super(Observable, self).__init__(**kwargs)

> class Widget(Observable, AppServerSessionMixin):
>     def __init__(self, appserver, session):
>         self.appserver = appserver
>         self.session = session
>         # general widget initialization code here...
>         super(Widget, self).__init__()

This should be:

    def __init__(self, appserver, session, **kwargs):
        self.appserver = appserver
        self.session = session
        # …
        super(Widget, self).__init__(**kwargs)

> class VisualWidget(Widget):
>     def __init__(self, appserver, session):
>         # some more code here for visually visible widgets...
>         super(VisualWidget, self).__init__(appserver, session)

Since this function doesn't do anything with the parameters, it doesn't
need to have its own names for them:

    def __init__(self, **kwargs):
        # …
        super(VisualWidget, self).__init__(**kwargs)

> class BaseDesktop:
>     def __init__(self, appserver, session):
>         # general code for all desktops is here...
>         super(BaseDesktop, self).__init__(appserver, session)
>
> class BootstrapWidget(VisualWidget):
>     def __init__(self, appserver, session):
>         # bootstrap specific code for all widgets (not just desktops)
>         super(BootstrapWidget, self).__init__(appserver, session)

And again:

    def __init__(self, **kwargs):
        # …
        super(BaseDesktop, self).__init__(**kwargs)

    def __init__(self, **kwargs):
        # …
        super(BootstrapWidget, self).__init__(**kwargs)

> class BootstrapDesktop(BootstrapWidget, BaseDesktop):
>     def __init__(self, appserver, session):
>         # there is NOTHING else here, it just connects bootstrap widget
> implementation with desktop methods
>         super(BootstrapDesktop, self).__init__(appserver, session)

If the ‘__init__’ method literally does nothing except call the
superclass's method, don't define it in BootstrapDesktop at all.

And likewise for the rest: pass along all remaining arguments, and don't
write a do-nothing ‘__init__’.

> Of course, I could explicitly call constructors of base classes. But
> then I cannot use super() anywhere. Even not in places that are far
> down in the class hierarchy. This results in typing all method
> parameters many times, and also unnecesary refactoring.

You avoid this by the ‘**kwargs’ idiom (no need to name arguments you
don't explicitly handle), and by not writing any ‘__init__’ if you want
the default behaviour (call the superclass's method).

> This is time consuming and error prone.

As pointed out in Raymond Hettinger's article
<URL:https://rhettinger.wordpress.com/2011/05/26/super-considered-super/>,
multiple inheritance is inherently tricky and there is an irreducible
complexity that has to appear somewhere in your code.

> In fact, I could never safely use super() in any case when the method
> signature has changed. When signatures are changed, method resolution
> must be done manually anyway.

Use ‘**kwargs’, and always pass along the remaining ‘**kwargs’
minus-any-parameters-you-already-handled, to protect your existing
classes from unnecessary signature changes.

-- 
 \     “Creativity can be a social contribution, but only in so far as |
  `\         society is free to use the results.” —Richard M. Stallman |
_o__)                                                                  |
Ben Finney




More information about the Python-list mailing list