Nesting parameterized objects leads to instantiation errors

I am trying to design apps with a lot of nested parameterized classes, but I ran into a confusing situation.

If one runs the following minimal app:

import param
import panel as pn

class A(param.Parameterized):
    input_widget = param.ClassSelector(class_=pn.widgets.TextInput, default=pn.widgets.TextInput())
    
    @param.depends('input_widget.value', watch=True)
    def do_stuff(self):
        print("Hello Panel")

    def __panel__(self):
        return self.input_widget

class B(param.Parameterized):
    a = param.ClassSelector(class_=A, default=A())

    def __panel__(self):
        return self.a


pn.Column(B()).servable()

it fails to load with

AttributeError: 'TextInput' object has no attribute 'do_stuff'

This is very confusing for the user since the app just showing A works, so you’d expect it to work too if you embed A in B.

The issue seems to be related to some param copy-logic, and one way to work around this is by changing it to

class B(param.Parameterized):
    a = param.ClassSelector(class_=A, default=A(), instantiate=False)

    def __panel__(self):
        return self.a

But i am not sure that is correct if one has multiple instances of B. Maybe one would need to pass in a value for A each time, but that seems cumbersome too.

Is this pattern of nesting widgets fundamentally wrong or is this unexpected behavior?

@jbednar

Actually the instantiate=False solution does not really solve the issue, as it seems the widget, although it was displaying correctly, was not reacting properly to events/visibility.

The only workaround I found so far is to just use a= param.Parameter() and instantiate the object myself in a custom __init__.