What is the best way to define nested Parameters in Parameterized class

First approach:

  • is to create separate Parameterized classes
import param as pm

class B(pm.Parameterized):
    class A(pm.Parameterized):
        a = pm.Integer(10)
        b = pm.Number(0)
    
    a = A()
    b = pm.Integer(50)
    
    def __init__(self, **params):
        super().__init__(**params)
        
    @pm.depends("a.a", watch=True)
    def callback(self):
        print(self.a.a * 10)

Second approach:
- as Class Selector

import param as pm



class B(pm.Parameterized):
    class A(pm.Parameterized):
        a = pm.Integer(10)
        b = pm.Number(0)
    
    a = pm.ClassSelector(class_=A, default=A())
    b = pm.Integer(50)
    
    def __init__(self, **params):
        super().__init__(**params)
        
    @pm.depends("a.param", watch=True)
    def callback(self):
        print(self.a.a * 10)

Maybe there are another approaches?
What difference between them?

1 Like

The aim of nested parameters is to create something similar to nested dicts (or nested configs)

1 Like

Hi @Illia

I’ve would do something close to your second approach.

import param as pm

class X(pm.Parameterized):
        a = pm.Integer(10)
        b = pm.Number(0)

class Y(pm.Parameterized):
    c = pm.ClassSelector(class_=X)
    d = pm.Integer(50)

    def __init__(self, **params):
        if not "c" in params:
            params["c"]=X()

        super().__init__(**params)

    @pm.depends("c.param", watch=True)
    def callback(self):
        self.d+=1
        print("c", self.c.a, self.c.b)
        print("d", self.d)

If I add the test_case below to the script

def test_case():
    y=Y()
    for _ in range(0,2):
        y.c.a+=1
        y.c.b+=1

test_case()

and run python name_of_script.py I see

c 11 0
d 51
c 11 1
d 52
c 12 1
d 53
c 12 2
d 54
1 Like

For further inspiration check out the Param subobjects notebook.

1 Like

Oh, I missed this notebook.
Thank you

2 Likes

Hi @Alexboiboi

A general solution is something that is discussed here Full JSON/YAML support for Param · Issue #520 · .

If you provide a specific example we could for sure find a concrete solution.

1 Like

Hi @Marc,

thank you for your response.

I am currently working on a project where some graphical styling properties can be set by a user. So far I implemented a bunch of classes with setters and getters with some validation. It works but its is very verbose and with the param library I was hoping to make the code cleaner and later make the step to implement the parametrization via some GUI much simpler.

Ideally I would like to do something like:

import param

class Description(param.Parameterized):
    show = param.Boolean(default=None, allow_None=True)
    text = param.String()

class BaseStyle(param.Parameterized):
    displayname = param.String(default=None, allow_None=True)
    description = param.ClassSelector(Description, default=Description())
    
new_values = {
    "displayname": "some name",
    "description": {"show":True, "text":"some text"}
}

bs = BaseStyle()

# then update the `BaseStyle` class with a nested dictionary
bs.param.update(**new_values)

Hi @Marc , do you have any suggestions?

Hi @Alexboiboi !

Well I think that the .update method doesn’t allow you to update nested Parameters like that. It may be worthwhile opening a feature request (if there isn’t one already!) to see what the core devs think about it.

In the mean time you could try to use this function that takes a parameterized object and a nested dict and update each one of the parameters declared in the nested dict:

def update_with_nested_dict(parameterized, nested_dict):
    # Using `batch_call_watchers` because it has the same underlying
    # mechanism as with `param.update`
    # See https://param.holoviz.org/user_guide/Dependencies_and_Watchers.html?highlight=batch_call#batch-call-watchers
    with param.parameterized.batch_call_watchers(parameterized):
        for pname, value in nested_dict.items():
            if isinstance(getattr(parameterized, pname), param.Parameterized):
                if not isinstance(value, dict):
                    raise ValueError(
                        f'The nested parameterized parameter "{pname}" must be '
                        f'updated with a dictionary, not a {type(value).__name__}.')
                update_with_nested_dict(getattr(parameterized, pname), value)
            else:
                setattr(parameterized, pname, value)

And use it as follows:

update_with_nested_dict(bs, new_values)

I’ve just tried this function quickly on a simple example and it worked, but Param is a complex machinery so if you use that function in your code try to make sure it works as you expect.

@maximlt thank you very much for your response. I’ll try this out

2 Likes