[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

super or not super?

On Tue, Jul 16, 2019 at 6:05 PM Antoon Pardon <antoon.pardon at vub.be> wrote:
> On 16/07/19 09:18, Chris Angelico wrote:
> > On Tue, Jul 16, 2019 at 3:32 PM Ian Kelly <ian.g.kelly at gmail.com> wrote:
> >> Just using super() is not enough. You need to take steps if you want to
> >> ensure that you class plays nicely with MI. For example, consider the
> >> following:
> >>
> >> class C1:
> >>     def __init__(self, name):
> >>         self._name = name
> >>
> >> class C2(C1):
> >>     def __init__(self, name, value):
> >>         super().__init__(name)
> >>         self._value = value
> >>
> >> This usage of super is just fine for the single-inheritance shown here. But
> >> there are two reasons why this cannot be neatly pulled into an MI
> >> hierarchy. Can you spot both of them?
> > Well, obviously it's violating LSP by changing the signature of
> > __init__, which means that you have to be aware of its position in the
> > hierarchy. If you want things to move around smoothly, you HAVE to
> > maintain a constant signature (which might mean using *args and/or
> > **kwargs cooperatively).
> I guess the second problem is that C1 doesn't call super. Meaning that if
> someone else uses this in a multiple heritance scheme, and the MRO reaches
> C1, the call doesn't get propagated to the rest.

My understanding of this tiny hierarchy is that C1 is the root of the
cooperative subtree. Example:

class Pizza: # like C1
    def __init__(self, *, name="Generic Pizza", **kw):
        self.name = name
        if kw: raise TypeError("Unexpected keyword args - did you miss
a mixin?")

class MeatloversPizza(Pizza): # like C2
    def __init__(self, *, pepperoni=True, **kw):
        if pepperoni: self.ingredients = "hot"

class CheesyCrustPizza(Pizza): # alternate mixin
    def __init__(self, *, cheesetype="mozzarella", **kw):
        self.crust = cheesetype + "-stuffed"

class DecadentPizza(MeatloversPizza, CheesyCrustPizza): pass

The only change I've made here is to ensure that everyone's
cooperatively using **kwargs. You can instantiate a
DecadentPizza(cheesetype="cheddar", name="Eat Me", pepperoni=False),
and the different classes will pick up the parts they want. This sort
of hierarchy works just fine, since MeatloversPizza is using super().

The signature of all __init__ functions is (self, *, **kw), with the
variant that the arguments specifically wanted by this class are
listed in a more discoverable way (rather than just popping them out
of kw before calling super's init). It's therefore fine for
MeatloversPizza to chain into CheesyCrustPizza.