How to dynamically assign dictionary to param.Selecotor.objects

I get an error when I try to dynamically assign a dictionary to Selector.objects.
Is there any way to dynamically change the choices while keeping the display name and internal values?

I tried the following two methods, but each resulted in a different error.
param version: 1.13.0

import param

class App(param.Parameterized):
    selector = param.Selector()
    
    def update_selector(self):
        self.param.selector.objects = {
            "0:None": None, "1:One": "one", "2:Two": "two", "3:Three": "three"}
        self.selector = "one"

app = App()
app.update_selector()
Error message1

AttributeError Traceback (most recent call last)
in
9
10 app = App()
—> 11 app.update_selector()

in update_selector(self)
6 def update_selector(self):
7 self.param.selector.objects = {“0:None”: None, “1:One”: “one”, “2:Two”: “two”, “3:Three”: “three”}
----> 8 self.selector = “one”
9
10 app = App()

/xxx/lib/python3.8/site-packages/param/parameterized.py in _f(self, obj, val)
365 instance_param = getattr(obj, ‘_instance__params’, {}).get(self.name)
366 if instance_param is not None and self is not instance_param:
→ 367 instance_param.set(obj, val)
368 return
369 return f(self, obj, val)

/xxx/lib/python3.8/site-packages/param/parameterized.py in _f(self, obj, val)
367 instance_param.set(obj, val)
368 return
→ 369 return f(self, obj, val)
370 return _f
371

/xxx/lib/python3.8/site-packages/param/parameterized.py in set(self, obj, val)
1199 val = self.set_hook(obj,val)
1200
→ 1201 self._validate(val)
1202
1203 _old = NotImplemented

/xxx/lib/python3.8/site-packages/param/init.py in _validate(self, val)
1297 “”"
1298 if not self.check_on_set:
→ 1299 self._ensure_value_is_in_objects(val)
1300 return
1301

/xxx/lib/python3.8/site-packages/param/init.py in _ensure_value_is_in_objects(self, val)
1330 “”"
1331 if not (val in self.objects):
→ 1332 self.objects.append(val)
1333
1334 def get_range(self):

AttributeError: ‘dict’ object has no attribute ‘append’

import param

class App(param.Parameterized):
    selector = param.Selector(objects = {"0:None": None})
    
    def update_selector(self):
        self.param.selector.objects = {
            "0:None": None, "1:One": "one", "2:Two": "two", "3:Three": "three"}
        self.selector = "one"

app = App()
app.update_selector()
Error message2

ValueError Traceback (most recent call last)
in
9
10 app = App()
—> 11 app.update_selector()

in update_selector(self)
6 def update_selector(self):
7 self.param.selector.objects = {“0:None”: None, “1:One”: “one”, “2:Two”: “two”, “3:Three”: “three”}
----> 8 self.selector = “one”
9
10 app = App()

/xxx/lib/python3.8/site-packages/param/parameterized.py in _f(self, obj, val)
365 instance_param = getattr(obj, ‘_instance__params’, {}).get(self.name)
366 if instance_param is not None and self is not instance_param:
→ 367 instance_param.set(obj, val)
368 return
369 return f(self, obj, val)

/xxx/lib/python3.8/site-packages/param/parameterized.py in _f(self, obj, val)
367 instance_param.set(obj, val)
368 return
→ 369 return f(self, obj, val)
370 return _f
371

/xxx/lib/python3.8/site-packages/param/parameterized.py in set(self, obj, val)
1199 val = self.set_hook(obj,val)
1200
→ 1201 self._validate(val)
1202
1203 _old = NotImplemented

/xxx/lib/python3.8/site-packages/param/init.py in _validate(self, val)
1320 break
1321 items = ‘[’ + ', '.join(items) + limiter
→ 1322 raise ValueError("%s not in parameter%s’s list of possible objects, "
1323 “valid options include %s” % (val, attrib_name, items))
1324

ValueError: one not in parameter selector’s list of possible objects, valid options include [0:None, 1:One, 2:Two, 3:Three]

Hi @A.Maeda,

I think your trying to work with the value of the pair rather than the name key, if you change to name key of the dictionary you can update the selector like below but not for certain that’s what your looking for?

Thanks.
I tried that method, but since I wanted to retain the internal values, I would have to reflect them in another variable or something, as in the following code. If so, is it not necessary to specify dictionary for Selector.objects and should I simply specify the display name as a list?
I wish there was a way to do this without having separate variables…

import param
import panel as pn

pn.extension()

class App(param.Parameterized):
    selector = param.Selector(objects = {"0:None": None})
    selected_value = param.String(allow_None=True)
    
    def __init__(self, **params):
        super().__init__(**params)
        self.update_selector()
    
    def update_selector(self):
        self.selector_map = {"0:None": None, "1:One": "one", "2:Two": "two", "3:Three": "three"}
        self.param.selector.objects = self.selector_map
        self.selector = "1:One"
    
    @param.depends("selector", watch=True)
    def update_selected_value(self):
        self.selected_value = self.selector_map[self.selector]

    @pn.depends("selected_value", watch=True)
    def display_selected_value(self):
        # To confirm value
        return self.selected_value

app = App()
layout = pn.Row(app.param.selector, app.display_selected_value)
layout

Maybe the below?

import param
import panel as pn

pn.extension()

class Select_from_supplied_dictionary(param.Parameterized):
    
    selection = param.Selector(objects = {"0:None": None, "1:One": "one", "2:Two": "two", "3:Three": "three"}, default="one")
    
    @param.depends('selection')
    def value(self):
        return (self.selection)
    
app = Select_from_supplied_dictionary()

pn.Column(app,app.value)

As shown below, I would like to apply an externally provided map to the choices, and possibly update the choices with some kind of trigger. However, it seems that if objects is changed after definition, it is not retained as a dictionary, but only a list of keys is retained.

import param
import panel as pn

pn.extension()

class Data():
    def get_map(self, flag):
        if flag:
            return {"0:None": None, "3:Three": "three", "4:Four": "four"}
        else:
            return {"0:None": None, "1:One": "one", "2:Two": "two"}

class App(param.Parameterized):
    action = param.Action(lambda x: x.update_selector(), label="Update")
    selector = param.Selector(objects={"0:None": None})

    def __init__(self, data, **params):
        super().__init__(**params)
        self.data = data
        self.param.selector.objects = self.data.get_map(False)

    @pn.depends("selector", watch=True)
    def display_selector(self):
        return self.selector

    def update_selector(self):
        self.param.selector.objects = self.data.get_map(True)

app = App(data=Data())
layout = pn.Row(pn.Column(app.param.selector, app.param.action), app.display_selector)
layout

the actual param SW in param/init.py indicates, that if you initialize the .objects with a dict, it stores the dict in a variable called .names.

And it looks like you can access it:

import param

class Test(param.Parameterized):
    x = param.Selector(objects={'x': 'X-factor', 'y': 'Y-factor'})

test = Test()
print(test.param.x.names)
print(test.param.x.objects)

Not sure if that’s what you’re after and can build something around that

Out of curiosity I ran a few tests.
Long story short - if you assign the new dictionary to both the self.param.selector.name and the self.param.selector.objects the widget gets properly updated (showing the new labels and the obj selected). If you only update the .objects, the widget will show the actual updated list of objects, not the labels.

import param
import panel as pn
pn.extension()

class Test(param.Parameterized):
    x = param.Selector(
            default='X-obj', check_on_set=True,
            objects={'x-label': 'X-obj', 'y-label': 'Y-obj'})

    def debug(self, msg=''):
        print(f'---- {self}:\t\t========== {msg} =============')
        print(f'\tvalue:   {self.x}')
        print(f'\tnames:   {self.param.x.names}')
        print(f'\tobjects: {self.param.x.objects}')
        print(f'\trange:   {self.param.x.get_range()}')
        #print(f'\twatchers:')
        #for element, details in self.param.x.watchers.items():
        #    print(f'\t\t{element}: \t{details}\n') 
             
test = Test()
widget = pn.widgets.Select.from_param(test.param.x)
widget.show()

test.debug('With WIDGET setup')
print(f'---- Widget: {widget.param.values()}')

import param
import panel as pn
pn.extension()

class Test(param.Parameterized):
    x = param.Selector(
            default='X-obj', check_on_set=True,
            objects={'x-label': 'X-obj', 'y-label': 'Y-obj'})

    def debug(self, msg=''):
        print(f'---- {self}:\t\t========== {msg} =============')
        print(f'\tvalue:   {self.x}')
        print(f'\tnames:   {self.param.x.names}')
        print(f'\tobjects: {self.param.x.objects}')
        print(f'\trange:   {self.param.x.get_range()}')
        #print(f'\twatchers:')
        #for element, details in self.param.x.watchers.items():
        #    print(f'\t\t{element}: \t{details}\n') 
             
test = Test()
widget = pn.widgets.Select.from_param(test.param.x)
widget.show()

test.debug('With WIDGET setup')
print(f'---- Widget: {widget.param.values()}')

modify = {'a-label': 'A-obj', 'b-label': 'B-obj'}

#test.param.x.names = modify
test.param.x.objects = modify.values()
test.x = 'B-obj'

test.debug('After change to "a/b"')
print(f'---- Widget: {widget.param.values()}')

modify = {'a-label': 'A-obj', 'b-label': 'B-obj'}

#test.param.x.names = modify
test.param.x.objects = modify.values()
test.x = 'B-obj'

test.debug('After change to "a/b", objects only')
print(f'---- Widget: {widget.param.values()}')

--- <Test Test00113>:		========== After change to "a/b", objects only =============
	value:   B-obj
	names:   {'x-label': 'X-obj', 'y-label': 'Y-obj'}
	objects: dict_values(['A-obj', 'B-obj'])
	range:   OrderedDict([('A-obj', 'A-obj'), ('B-obj', 'B-obj')])
---- Widget: {'align': 'start', 'aspect_ratio': None, 'background': None, 'css_classes': [], 'description': None, 'design': None, 'disabled': False, 'disabled_options': [], 'groups': None, 'height': None, 'height_policy': 'auto', 'loading': False, 'margin': (5, 10), 'max_height': None, 'max_width': None, 'min_height': None, 'min_width': None, 'name': 'X', 'options': OrderedDict([('A-obj', 'A-obj'), ('B-obj', 'B-obj')]), 'size': 1, 'sizing_mode': None, 'styles': {}, 'stylesheets': [], 'tags': [], 'value': 'B-obj', 'visible': True, 'width': 300, 'width_policy': 'auto'}

modify = {'1-label': '1-obj', '2-label': '2-obj'}

test.param.x.names = modify
test.param.x.objects = modify.values()
test.x = '1-obj'

test.debug('After change to "1/2", objects & names')
print(f'---- Widget: {widget.param.values()}')

modify = {'1-label': '1-obj', '2-label': '2-obj'}

---- <Test Test00113>:		========== After change to "1/2" =============
	value:   1-obj
	names:   {'1-label': '1-obj', '2-label': '2-obj'}
	objects: dict_values(['1-obj', '2-obj'])
	range:   OrderedDict([('1-label', '1-obj'), ('2-label', '2-obj')])
---- Widget: {'align': 'start', 'aspect_ratio': None, 'background': None, 'css_classes': [], 'description': None, 'design': None, 'disabled': False, 'disabled_options': [], 'groups': None, 'height': None, 'height_policy': 'auto', 'loading': False, 'margin': (5, 10), 'max_height': None, 'max_width': None, 'min_height': None, 'min_width': None, 'name': 'X', 'options': OrderedDict([('1-label', '1-obj'), ('2-label', '2-obj')]), 'size': 1, 'sizing_mode': None, 'styles': {}, 'stylesheets': [], 'tags': [], 'value': '1-obj', 'visible': True, 'width': 300, 'width_policy': 'auto'}

Having said this, ultimately I think it’s down to the overall context and how complex your app is.
Usually

  • Widget selections lists are pretty static.
  • Need for extra labels for situations where the actual objects can’t provide useful ones, or
    you want to hide some complex string or something, …

If the lists are not static, are you switching between a set of predefined ones (like the continent/country example in Declare parameter dependencies — Panel v1.2.3 or they’re really dynamic - in which case you will also need to think about how to keep them in-sync across the whole app.

If labels are needed because you want to “hide” the actual object from the user, the question arises if you want to always pre-allocate the objects (eg. for performance reasons) or just create them on the fly once you know what the user has selected.

If I have dynamic selection-lists or selections with complex objects behind (eg. selecting “resources” that are not fixed or may change during run-time) I tend to just display/update a “List” with Ids/Names and handle the actual resource object allocation after user-selection.

Though I’m not a param, panel expert, so I may miss something or there may be more elegant ways to do this.

Thank you! Setting a dictionary to param.x.names was helpful in achieving the operation.
In fact, it was necessary to get the list of choices, both display names and internal values, from the database when the application was launched. The selected internal values needed to be referenced for use in another function.