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