Placement of super()

Hi, Does anyone know the rules for super() placement within a parameterized class __init__ method?

I can see that it allows me to make references to self within the __init__ itself, but its not so straight forward that we always put it at the beginning of the method as in this case straight from the panel docs.

from panel.viewable import Viewer

class EditableRange(Viewer):
    
    value = param.Range(doc="A numeric range.")
    
    width = param.Integer(default=300)
    
    def __init__(self, **params):
        self._start_input = pn.widgets.FloatInput()
        self._end_input = pn.widgets.FloatInput(align='end')
        super().__init__(**params)
        self._layout = pn.Row(self._start_input, self._end_input)
        self._sync_widgets()

In this case, super is neither at the beginning nor the end of the method. What are the guiding principles the practitioner should follow to make this decision?

1 Like

I would say that totally depends on your use case.
Calling super().__init__() just executes the initialization code of the parent class.
So if you want to add extra arguments to that call, or ensure other side effects before, you can call super() later.
In your example i would assume, havent executed it, that you can put the super() call at the beginning, because adding the two widgets doesnt change the behaviour of the parent init.

Another example is here Param subobjects — Panel 0.12.4 documentation, where you can see in this code

    def __init__(self, **params):
        if 'style' not in params:
            params['style'] = Style(name='Style')
        super(Shape, self).__init__(**params)
        self.figure = figure(x_range=(-1, 1), y_range=(-1, 1), sizing_mode="stretch_width", height=400)
        self.renderer = self.figure.line(*self._get_coords())
        self._update_style()

the super() call is delayed, because we add a default to the keyword arguments

2 Likes

I agree with @Stubatiger

The only thing you should not do before super().__init__ is use the parameters. They have not yet been initialized.

2 Likes

Thanks guys, makes sense.

Yep, I agree with all that; use super() early in the constructor by default, but sometimes other things need to be done first, and if so, go for it!

Thanks. Also, nice work on the Dashboard showdown today, great to hear your perspectives.

2 Likes

I like your question @jbogaardt ! I’ve also been confused many times about how to declare things in __init__, and why it’s done in such a way when I read a code base.

The example you show is from the user guide of Panel (Custom Components — Panel 0.12.4 documentation), I’ll show here the complete version of it:

from panel.viewable import Viewer

class EditableRange(Viewer):
    
    value = param.Range(doc="A numeric range.")
    
    width = param.Integer(default=300)
    
    def __init__(self, **params):
        self._start_input = pn.widgets.FloatInput()
        self._end_input = pn.widgets.FloatInput(align='end')
        super().__init__(**params)
        self._layout = pn.Row(self._start_input, self._end_input)
        self._sync_widgets()
    
    def __panel__(self):
        return self._layout
    
    @param.depends('value', 'width', watch=True)
    def _sync_widgets(self):
        self._start_input.name = self.name
        self._start_input.value = self.value[0]
        self._end_input.value = self.value[1]
        self._start_input.width = self.width//2
        self._end_input.width = self.width//2
        
    @param.depends('_start_input.value', '_end_input.value', watch=True)
    def _sync_params(self):
        self.value = (self._start_input.value, self._end_input.value)
    
range_widget = EditableRange(name='Range', value=(0, 10))

pn.Column(
    '## This is a custom widget',
    range_widget
)

In this example you actually have to declare self._start_input and self._end_input before calling super().__init__. If you do it after, you’ll get the following error (well at least before param 1.12.0, this has changed but that may well be a bug https://github.com/holoviz/param/issues/566):

...
~/miniconda3/envs/holoviz37/lib/python3.7/site-packages/param/parameterized.py in _getattr(obj, attr)
    280 def _getattrr(obj, attr, *args):
    281     def _getattr(obj, attr):
--> 282         return getattr(obj, attr, *args)
    283     return reduce(_getattr, [obj] + attr.split('.'))
    284 

AttributeError: 'EditableRange' object has no attribute '_start_input'

What happens there is that when super().__init__ is called param will try to register the dependencies that were declared, i.e. on value, width, _start_input.value and _end_input.value. It will succeed for the first two, but fail with _start_input.value since it doesn’t yet know about it. When the widgets are declared before, param then knows about them and can properly sets the watchers/dependencies. Even if that part feels quite magical to me, the widgets not being Parameters but Parameterized classes (it works since it depends their values, which are Parameters).

I’d also like to add a couple of comments about the __init__ in this example:

  • It is using **params which I got used to by now but I remember being confused by that, since the convention in the python world is to refer to keyword arguments with **kwargs. I thought at first that these must be some sort of special keyword arguments, while really If I’m correct they’re not, it’s just another convention.
  • By defining it with just def __init__(self, **params) I get a poor help in VSCode (see the image). It’s better with def __init__(self, value, width, **params)
    image

As for your example @Stubatiger , wouldn’t this be equivalent to that?

class Shape(param.Parameterized):

    radius = param.Number(default=1, bounds=(0, 1))
    
    style = param.Parameter(default=Style(name='Style'), precedence=3)

    def __init__(self, **params):
        super(Shape, self).__init__(**params)
        self.figure = figure(x_range=(-1, 1), y_range=(-1, 1), sizing_mode="stretch_width", height=400)
        self.renderer = self.figure.line(*self._get_coords())
        self._update_style()
3 Likes