Accessing parameters of nested parameterized objects when displaying in panel

Hi All

I’m quite new to panel and param and really like them. I’m trying to build an app that will be based on a moderately complicated data model, which I’m building in parameterized classes. These classes will sometimes be nested at various levels, but they should work independently too. This seems to be to be a very natural use case. I’ve expressed it quite abstractly, but for instance, if you have a Recipe object, it might have a list of Ingredient objects, and you might want to be able to handle each of those classes independently of the other some time. I like the declarative nature of param for this kind of task because it means you can declare the relationships between the data/classes and worry about displaying it or not when it comes to the UI.

So to pursue that example, if you’ve got a visualisation based on a Recipe (maybe showing downloads, reviews stats, etc) it could incorporate a tab or pane for focusing on an ingredient. Then the user might select an ingredient using a dropdown (i.e. Selector) and then the relevant pane/panes would update to show the stats for that ingredient.

So my question is how to get the reference to the selected instance in such a way that it can be used as the object that’s visualised in the relevant pane/s.

Displaying the following in panel works as I want (sticking with the Recipe case): recipe_instance.param — this displays the Selector object with a blue button and the button reveals the instance currently selected with it’s parameterized properties, which can be changed and which persist if you chance the instance using the Selector then change it back.

This is the functionality I want but I don’t want to be limited to displaying everything automatically in one place as with recipe_instance.param. I want to put the Selector in one place (e.g. sidebar) and the inner class parameters in another place (panes or tabs).

Here is a minimal example to show what I mean and some of the ways I would intuitively think it could be achieved. None of these work. They display the instance of the inner class that is the value of the Selector when the outer class is initiated, but they do not update to another instance if the value of the Selector is changed.

I would be extremely grateful for any pointers—perhaps there’s an obvious way to do this that I’m missing.

import panel as pn
import param


class Inner(param.Parameterized):
    target_param = param.String(default="default")
    dependent_param = param.String(constant=True)

    @param.depends('target_param', watch=True)
    def _update_dependent_param(self):
        with param.edit_constant(self):
            self.dependent_param = self.target_param


inner1 = Inner(target_param="inner1 parameter")
inner2 = Inner(target_param="inner2 parameter")
inner_objs = [inner1, inner2]


class Outer(param.Parameterized):
    instance_of_Inner = param.Selector(default=inner_objs[0],
                                       objects=inner_objs)

outer = Outer()

references = {
    '(1) outer.param.instance_of_Inner \n\n this is the Selector object that will choose which instance of class Inner is active' : outer.param.instance_of_Inner,
    '(2) outer.instance_of_Inner  \n\n  this is the value of that Selector object (an instance of Inner) and I would expect it not to update' : outer.instance_of_Inner,
    '(3) outer.instance_of_Inner.param  \n\n  this should be the instance as a Parameterized object, I would expect this to change reflecting changes in the selection made with the Selector' : outer.instance_of_Inner.param,
    '(4) outer.param  \n\n for good measure, to see what calling the Outer as a param object does': outer.param,
    '(5) outer  \n\n for good measure, to see what calling the Outer as a value object does': outer,
}

panes = [pn.Column(k, references[k]) for k in references.keys()]
print(panes)
pn.Column(*panes).servable()
1 Like

PS, to put the question another way:

Searching through previous posts I found a mention of the following example notebook:

https://panel.holoviz.org/gallery/param/param_subobjects.html#param-gallery-param-subobjects

This nearly does what I want because it has nested classes, but what I need to do is get a live reference to the inner class (i.e. one that updates) through which I can access its parameters as parameters.

In the case of the Subobjects notebook, that would mean getting a reference to the currently selected shape through which one could access, display or modify a parameter of that shape (e.g. line_width).

Hi @JonathanMair,

What about something like this from @fdorssers whom made some progress, sounds similar to what you may be looking for though not certain; came across it searching for child classes

How do you pass a parameter object (from param) to a child class and listen to changes there, and propagate the UI changes back up - Panel - HoloViz Discourse

Thanks, Carl.

Are you looking for something like this?

Thanks for the link Carl — yes, that is indeed what I was looking to do, but I agree with the author of that post that it’s a bit of an unwieldy way of doing it.

Yes, excellent - thanks!

Further to @jbednar’s very helpful solution above, this comment for anyone reading this in the same place I was…if you need to access a subset of the params of the inner class then this seems to be one way of doing that…binding the param.objects() function which returns a dictionary of the instance’s Parameter objects, then subscripting to select the one(s) you need, like this:

pn.bind(lambda x: x.param.objects(instance=True)['target_param'], outer.param.instance_of_Inner)

1 Like

Sure, though .param provides convenient member-style access, so you can do:

If this approach directly meets your needs and wasn’t obvious from the docs, please consider where that would best show up in the Panel (and/or Param?) docs and then file an issue suggesting what to add there, and/or make a PR actually adding that info to the docs!

Sure, though .param provides convenient member-style access

Got it, thanks — in the particular case I was working on, I’m selecting individual parameters of the inner class using a list of strings in order to vary the list it dynamically so the dictionary returned from objects() was useful for that

I will think about it — but at the moment I feel I don’t quite understand it enough!

Right; member-style access is good for hard-coding specific parameters, and the dictionary is good for selecting dynamically. It does take a while to understand how to understand which bits of the code get re-executed automatically and which ones you have to wrap in a lambda or other function to ensure that it’s re-executed when needed. Once that’s clear, improvements to the docs that address the difficulties you encountered would be very helpful, since it’s harder for people who have been using Param for decades to see the best way to describe it for new users!

1 Like

Hi - though the above solutions worked in the specific case I was stuck with, I’m still struggling with this general area of passing references through panel.bind. Rather than posting more specific cases, I’m trying to understand the principle and going over the documentation and other posts here. I think I have found my sticking point (or one of them!) now…

I think I’m beginning to understand because of another of your answers, @jbednar (Get the Values of a Rangeslider - #4 by jbednar). There you mention that the function part of the pn.bind() wrapper treats a parameter object passed as an argument as an alias for the param value.

To apply this to the solution you provided above:

pn.Row(
    outer, 
    pn.bind(lambda x: x.param, outer.param.instance_of_inner)
)

…the outer.param.instance_of_inner that is passed into the lambda function is the live parameter object that holds the parameter value (an instance of class Inner). That’s important because if the parameter value is passed instead, that’s just the value at the time the bind() function is called, it won’t be live.

However, inside the function that is the first argument of the bind() call, i.e. in this case the lambda function, the param object that was passed is turned silently into a value. In this case the value of the parameter instance_of_inner is a Parameterized class, the lambda function is able to use .param on it to access the parameters of the instance of class Inner.

If this is right then I am finally beginning to understand this behaviour :sweat_smile:. It makes PERFECT sense because what needs to be watched is the object and what needs to be put into action by the function is the value. However, I am finding it highly counter-intuitive…I guess I’ll get used to it, but I don’t think this is explicitly addressed in the documentation.

That’s the behavior of pn.bind: it acts exactly like functools.partial except that it has special behavior if you pass it a Panel widget or a Parameter object. If you pass a Panel widget, it fetches the Parameter object called value that is held by that widget, and acts as if you passed that instead. I.e. passing widget is precisely the same as passing widget.param.value, which in the common and typical case where you want to watch the value (and not the range or some other Parameter of the widget) is concise and (to us!) clear.

So whether you passed a widget or a Parameter object, pn.bind has gotten ahold of a Parameter object. It then acts just as if you had passed the Parameter’s value (even though you really passed the Parameter object), except that it is watching the object for changes to the Parameter’s value and it then re-invokes the function with the new value whenever that value changes.

In your case, the Parameter’s value is indeed a Parameterized object (not an individual Parameter object, but a Parameterized object that holds potentially many Parameter objects). So whenever that object changes, the lambda function accesses .param.instance_of_inner on the new object, displaying that.

So, pn.bind is what accepts Parameter objects and then invokes the underlying function with the Parameter value, thus bridging between Panel and Param’s world of Parameters and the underlying function’s world of actual values.

I think you’ve got it figured out, but I figured it didn’t hurt to be very specific (unless I misspoke somewhere and made it worse!). If there are changes to the pn.bind docs that could make this clear, i.e. that bind accepts Parameter objects and then invokes the function on Parameter values, please suggest appropriate wording to that effect!

1 Like

@jbednar – got it, thanks very much for the detailed explanation, that’s very helpful.

I’m still having problems understanding the way param behaves from an OO point of view. Now that I’ve understood pn.bind() better I realise I have other assumptions about it that are proving to be wrong. I will post in the param board because I don’t think it’s on topic any more for this post.

Thanks again!

1 Like