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 value
s, 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 withdef __init__(self, value, width, **params)
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()