Cryptic error message when forgetting to call super().__init__ on Parameterized
ALL software version info
Holoviews 1.13.5 Param 1.10.1 Panel 0.10.3
Description of expected behavior and the observed behavior
Extending a Parameterized class without calling init results in a cryptic message. See below It would be nice to have a better message to remind user to call super().init(**kwargs)
AttributeError: 'TestStreamParam' object has no attribute '_param_watchers'
Minimal working example
import param
import panel as pn
class TestParam(param.Parameterized):
onoff = param.Boolean()
def __init__(self, initial=False):
onoff = initial
def view(self):
print(self.onoff)
Using it like this gives the error
tp = TestParam()
pn.Row(tp.param)
See stack trace below
Fix
The fix is quite simple, Just add **kwargs to the init method and call the super
class TestParam(param.Parameterized):
onoff = param.Boolean()
def __init__(self, initial=False, **kwargs):
super().__init__(**kwargs)
onoff = initial
....
Stack traceback and/or browser JavaScript console output
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-9-6053fe58e559> in <module>
1 tsp=TestStreamParam()
2
----> 3 pn.Row(tsp.param)
~\AppData\Local\Continuum\anaconda3\envs\dms\lib\site-packages\panel\layout\base.py in __init__(self, *objects, **params)
358 "as positional arguments or as a keyword, "
359 "not both." % type(self).__name__)
--> 360 params['objects'] = [panel(pane) for pane in objects]
361 elif 'objects' in params:
362 params['objects'] = [panel(pane) for pane in params['objects']]
~\AppData\Local\Continuum\anaconda3\envs\dms\lib\site-packages\panel\layout\base.py in <listcomp>(.0)
358 "as positional arguments or as a keyword, "
359 "not both." % type(self).__name__)
--> 360 params['objects'] = [panel(pane) for pane in objects]
361 elif 'objects' in params:
362 params['objects'] = [panel(pane) for pane in params['objects']]
~\AppData\Local\Continuum\anaconda3\envs\dms\lib\site-packages\panel\pane\base.py in panel(obj, **kwargs)
49 if kwargs.get('name', False) is None:
50 kwargs.pop('name')
---> 51 pane = PaneBase.get_pane_type(obj, **kwargs)(obj, **kwargs)
52 if len(pane.layout) == 1 and pane._unpack:
53 return pane.layout[0]
~\AppData\Local\Continuum\anaconda3\envs\dms\lib\site-packages\panel\param.py in __init__(self, object, **params)
189 'object', 'parameters', 'name', 'display_threshold', 'expand_button',
190 'expand', 'expand_layout', 'widgets', 'show_labels', 'show_name'])
--> 191 self._update_widgets()
192
193 def __repr__(self, depth=0):
~\AppData\Local\Continuum\anaconda3\envs\dms\lib\site-packages\panel\param.py in _update_widgets(self, *events)
258 self._widgets = {}
259 else:
--> 260 self._widgets = self._get_widgets()
261
262 alias = {'_title': 'name'}
~\AppData\Local\Continuum\anaconda3\envs\dms\lib\site-packages\panel\param.py in _get_widgets(self)
540 else:
541 widgets = []
--> 542 widgets += [(pname, self.widget(pname)) for pname in self._ordered_params]
543 return OrderedDict(widgets)
544
~\AppData\Local\Continuum\anaconda3\envs\dms\lib\site-packages\panel\param.py in <listcomp>(.0)
540 else:
541 widgets = []
--> 542 widgets += [(pname, self.widget(pname)) for pname in self._ordered_params]
543 return OrderedDict(widgets)
544
~\AppData\Local\Continuum\anaconda3\envs\dms\lib\site-packages\panel\param.py in widget(self, p_name)
494 if 'step' in kw:
495 watchers.append(self.object.param.watch(link, p_name, 'step'))
--> 496 watchers.append(self.object.param.watch(link, p_name))
497
498 options = kwargs.get('options', [])
~\AppData\Local\Continuum\anaconda3\envs\dms\lib\site-packages\param\parameterized.py in watch(self_, fn, parameter_names, what, onlychanged, queued)
1892 onlychanged=onlychanged, parameter_names=parameter_names,
1893 what=what, queued=queued)
-> 1894 self_._watch('append', watcher, what)
1895 return watcher
1896
~\AppData\Local\Continuum\anaconda3\envs\dms\lib\site-packages\param\parameterized.py in _watch(self_, action, watcher, what, operation)
1875
1876 if self_.self is not None and what == "value":
-> 1877 watchers = self_.self._param_watchers
1878 if parameter_name not in watchers:
1879 watchers[parameter_name] = {}
AttributeError: 'TestStreamParam' object has no attribute '_param_watchers'
Thanks! I agree that this is a cryptic error message, but I'm having trouble thinking of anything we can do in Param to detect it. If you override the constructor in this way, Parameterized's own constructor will never get called, so we can't put anything to detect it in there. Maybe it could go in the metaclass somewhere, detecting that the super constructor never got called, but if so I wouldn't know how to go about that.
@philippjfr, were you maybe imagining that Panel could detect this, not Param? Panel could presumably short-circuit this error message by testing for proper construction earlier, but if so this would be an issue for Panel, not Param.
I could imagine that a sentinel flag could be set and then any access to parameterized.param, __getattr__ and __setattr__ would generate a clear error message. I'd say that would catch this error in (almost) all cases.
Hmm. I agree that would catch the message, but I'm a bit concerned that it would require slowing down every attribute access, just to improve what seems a fairly obscure error condition.
Maybe we could instead put .param/getattr/setattr behind a try/except where the first thing we check is on exception if the object was not properly initialized, and if so explain that it needs to be initialized for all Parameterized features to be available, and otherwise reraise the original exception. Seems a bit tricky!