Default value of `allow_None` for Selectors
The Selector Parameter is instantiated with allow_None=None. All of its descendants inherit this.
>>> import param
>>> print(param.concrete_descendents(param.Selector))
{'Selector': param.Selector,
'ObjectSelector': param.ObjectSelector,
'FileSelector': param.FileSelector,
'ListSelector': param.ListSelector,
'MultiFileSelector': param.MultiFileSelector}
It is the only case of allow_None set to None, otherwise it's set to either True or False.
Functionally, setting it to False by default would I think lead to the exact same behaviors. So I wonder why it's None (it's pretty old code that's inherited from when it was in ObjectSelector)? That's maybe the source of this rather dry issue opened by Chris a while ago (https://github.com/holoviz/param/issues/106) ?
I find the semantics of allow_None for Selectors a bit confusing.
First, it's possible to fool Param (or yourself?), by either not allowing None by including it in the list of allowed objects, or by not allowing it but by setting check_on_set to False.
import param
class P(param.Parameterized):
s = param.Selector(objects=[1, 2, None], allow_None=False)
t = param.Selector(objects=[1, 2], allow_None=False, check_on_set=False)
p = P()
p.s = None
p.t = None
print(p.param.s.allow_None, p.param.t.allow_None)
# False False
I find strange that it's possible to have a default value of None, set the parameter to another valid value, and not be able to set it back to None.
import param
class P(param.Parameterized):
s = param.ListSelector(default=None, objects=[1, 2])
p = P()
assert p.s is None
p.s = [1]
p.s = None
ValueError Traceback (most recent call last)
Cell In[73], line 12
8 assert p.s is None
10 p.s = [1]
---> 12 p.s = None
File ~/dev/param_main/param/parameterized.py:375, in instance_descriptor.<locals>._f(self, obj, val)
373 instance_param.__set__(obj, val)
374 return
--> 375 return f(self, obj, val)
File ~/dev/param_main/param/parameterized.py:1213, in Parameter.__set__(self, obj, val)
1210 if hasattr(self, 'set_hook'):
1211 val = self.set_hook(obj,val)
-> 1213 self._validate(val)
1215 _old = NotImplemented
1216 # obj can be None if __set__ is called for a Parameterized class
File ~/dev/param_main/param/__init__.py:1918, in ListSelector._validate(self, val)
1916 return
1917 if not isinstance(val, list):
-> 1918 raise ValueError("ListSelector parameter %r only takes list "
1919 "types, not %r." % (self.name, val))
1920 for o in val:
1921 super(ListSelector, self)._validate(o)
ValueError: ListSelector parameter 's' only takes list types, not None.
I'm pretty sure that allow_None=None was because before we had Undefined, we used None to indicate "not set by the user", and checked for None to determine appropriate default behavior. Now it's hard to imagine why that special case would need to be preserved.
Even supporting allow_None at all seems redundant for list-based Selectors, because people can just put None in the list of objects if they want to allow it.
Still, in the interest of consistency, my vote is for changing Selectors to allow_None=Undefined as an argument bottoming out in allow_None=False with the same behavior as for non-Selector Parameters.
Selectors need more work in general, that change can happen when this work is undertook, as as is it would not fix anything, except my need for consistency :). Moving to post Param 2.0.