meaning of the super keyword in the parent class python

770 views Asked by At

I do not understand the meaning of the super keyword when it is not used in a child class.

The question comes from this class here that I found on a git hub project where I am working (the link is https://github.com/statsmodels/statsmodels/pull/2374/files)

Look for example at the fit method where the code res = super(PenalizedMixin, self).fit(method=method, **kwds) + appears

"""
+Created on Sun May 10 08:23:48 2015
+
+Author: Josef Perktold
+License: BSD-3
+"""
+
+import numpy as np
+from ._penalties import SCADSmoothed
+
+class PenalizedMixin(object):
+    """Mixin class for Maximum Penalized Likelihood
+
+
+    TODO: missing **kwds or explicit keywords
+
+    TODO: do we really need `pen_weight` keyword in likelihood methods?
+
+    """
+
+    def __init__(self, *args, **kwds):
+        super(PenalizedMixin, self).__init__(*args, **kwds)
+
+        penal = kwds.pop('penal', None)
+        # I keep the following instead of adding default in pop for future changes
+        if penal is None:
+            # TODO: switch to unpenalized by default
+            self.penal = SCADSmoothed(0.1, c0=0.0001)
+        else:
+            self.penal = penal
+
+        # TODO: define pen_weight as average pen_weight? i.e. per observation
+        # I would have prefered len(self.endog) * kwds.get('pen_weight', 1)
+        # or use pen_weight_factor in signature
+        self.pen_weight =  kwds.get('pen_weight', len(self.endog))
+
+        self._init_keys.extend(['penal', 'pen_weight'])
+
+
+
+    def loglike(self, params, pen_weight=None):
+        if pen_weight is None:
+            pen_weight = self.pen_weight
+
+        llf = super(PenalizedMixin, self).loglike(params)
+        if pen_weight != 0:
+            llf -= pen_weight * self.penal.func(params)
+
+        return llf
+
+
+    def loglikeobs(self, params, pen_weight=None):
+        if pen_weight is None:
+            pen_weight = self.pen_weight
+
+        llf = super(PenalizedMixin, self).loglikeobs(params)
+        nobs_llf = float(llf.shape[0])
+
+        if pen_weight != 0:
+            llf -= pen_weight / nobs_llf * self.penal.func(params)
+
+        return llf
+
+
+    def score(self, params, pen_weight=None):
+        if pen_weight is None:
+            pen_weight = self.pen_weight
+
+        sc = super(PenalizedMixin, self).score(params)
+        if pen_weight != 0:
+            sc -= pen_weight * self.penal.grad(params)
+
+        return sc
+
+
+    def scoreobs(self, params, pen_weight=None):
+        if pen_weight is None:
+            pen_weight = self.pen_weight
+
+        sc = super(PenalizedMixin, self).scoreobs(params)
+        nobs_sc = float(sc.shape[0])
+        if pen_weight != 0:
+            sc -= pen_weight / nobs_sc  * self.penal.grad(params)
+
+        return sc
+
+
+    def hessian_(self, params, pen_weight=None):
+        if pen_weight is None:
+            pen_weight = self.pen_weight
+            loglike = self.loglike
+        else:
+            loglike = lambda p: self.loglike(p, pen_weight=pen_weight)
+
+        from statsmodels.tools.numdiff import approx_hess
+        return approx_hess(params, loglike)
+
+
+    def hessian(self, params, pen_weight=None):
+        if pen_weight is None:
+            pen_weight = self.pen_weight
+
+        hess = super(PenalizedMixin, self).hessian(params)
+        if pen_weight != 0:
+            h = self.penal.deriv2(params)
+            if h.ndim == 1:
+                hess -= np.diag(pen_weight * h)
+            else:
+                hess -= pen_weight * h
+
+        return hess
+
+
+    def fit(self, method=None, trim=None, **kwds):
+        # If method is None, then we choose a default method ourselves
+
+        # TODO: temporary hack, need extra fit kwds
+        # we need to rule out fit methods in a model that will not work with
+        # penalization
+        if hasattr(self, 'family'):  # assume this identifies GLM
+            kwds.update({'max_start_irls' : 0})
+
+        # currently we use `bfgs` by default
+        if method is None:
+            method = 'bfgs'
+
+        if trim is None:
+            trim = False  # see below infinite recursion in `fit_constrained
+
+        res = super(PenalizedMixin, self).fit(method=method, **kwds)
+
+        if trim is False:
+            # note boolean check for "is False" not evaluates to False
+            return res
+        else:
+            # TODO: make it penal function dependent
+            # temporary standin, only works for Poisson and GLM,
+            # and is computationally inefficient
+            drop_index = np.nonzero(np.abs(res.params) < 1e-4) [0]
+            keep_index = np.nonzero(np.abs(res.params) > 1e-4) [0]
+            rmat = np.eye(len(res.params))[drop_index]
+
+            # calling fit_constrained raise
+            # "RuntimeError: maximum recursion depth exceeded in __instancecheck__"
+            # fit_constrained is calling fit, recursive endless loop
+            if drop_index.any():
+                # todo : trim kwyword doesn't work, why not?
+                #res_aux = self.fit_constrained(rmat, trim=False)
+                res_aux = self._fit_zeros(keep_index, **kwds)
+                return res_aux
+            else:
+                return res
+
+

I have tried to reproduce this code with a simpler example but it does not work:

class A(object):
    def __init__(self):
        return

    def funz(self, x):
        print(x)

    def funz2(self, x):
        llf = super(A, self).funz2(x)
        print(x + 1)

a = A()
a.funz(3)
a.funz2(4)


Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/donbeo/Desktop/prova.py", line 15, in <module>
    a.funz2(4)
  File "/home/donbeo/Desktop/prova.py", line 10, in funz2
    llf = super(A, self).funz2(x)
AttributeError: 'super' object has no attribute 'funz2'
>>> 
2

There are 2 answers

4
jonrsharpe On BEST ANSWER

You should always use super, because otherwise classes may get missed out, particularly in a multiple inheritance scenario (which is inevitable where mix-in classes are being used). For example:

class BaseClass(object):

    def __init__(self):
        print 'BaseClass.__init__'


class MixInClass(object):

    def __init__(self):
        print 'MixInClass.__init__'


class ChildClass(BaseClass, MixInClass):

    def __init__(self):
        print 'ChildClass.__init__'
        super(ChildClass, self).__init__()  # -> BaseClass.__init__


if __name__ == '__main__':
    child = ChildClass()

gives:

ChildClass.__init__
BaseClass.__init__

missing out MixInClass.__init__, whereas:

class BaseClass(object):

    def __init__(self):
        print 'BaseClass.__init__'
        super(BaseClass, self).__init__()  # -> MixInClass.__init__


class MixInClass(object):

    def __init__(self):
        print 'MixInClass.__init__'
        super(MixInClass, self).__init__()  # -> object.__init__


class ChildClass(BaseClass, MixInClass):

    def __init__(self):
        print 'ChildClass.__init__'
        super(ChildClass, self).__init__()  # -> BaseClass.__init__


if __name__ == '__main__':
    child = ChildClass()

gives:

ChildClass.__init__
BaseClass.__init__
MixInClass.__init__

ChildClass.__mro__, the "method resolution order", is the same in both cases:

(<class '__main__.ChildClass'>, <class '__main__.BaseClass'>, <class '__main__.MixInClass'>, <type 'object'>)

Both BaseClass and MixInClass inherit only from object (i.e. they are "new-style" classes), but you still need to use super to ensure that any other implementations of the method in classes in the MRO get called. To enable this usage, object.__init__ is implemented, but doesn't really do much!

3
Daniel Roseman On

PenalizedMixin is a child class: it is a child of object.

However, as the name implies, it meant to be a mixin. That is, it is intended to be used as one parent in a multiple inheritance scenario. super calls the next class in the method resolution order, which is not necessarily the parent of that class.

In any case, I don't understand your "simpler" example. The reason the original code works is that the superclass does have an __init__ method. object does not have a funz2 method.