Is there a best practice way to create a param "property"?

I am trying to create something like a parameter property on Parameterized class. Because I would like be able to display and layout the properties in Panel via the pn.Param parameter.

As far as I can see this is not supported “out of the box” (?) so I’m creating the below workaround

import param


class SpreadFeatures(param.Parameterized):
    """A class containing features of a spread"""

    mean_10d_5d_rolling = param.Number()
    std_10d_5d_rolling = param.Number()

    mean_to_std_10d_5d_rolling = param.Number(None, constant=True)

    @param.depends("mean_10d_5d_rolling", "std_10d_5d_rolling", watch=True)
    def set_mean_to_std_10d_5d_rolling(self):
        print("update")
        with param.edit_constant(self):
            if self.std_10d_5d_rolling:
                self.mean_to_std_10d_5d_rolling = self.mean_10d_5d_rolling / self.std_10d_5d_rolling
            else:
                self.mean_to_std_10d_5d_rolling = None


features = SpreadFeatures(mean_10d_5d_rolling=1, std_10d_5d_rolling=2)

When I use IPython I see

In [1]: %run scripts\issues\issue_param_constructor.py

In [2]: features
Out[2]: SpreadFeatures(mean_10d_5d_rolling=1, mean_to_std_10d_5d_rolling=None, name='SpreadFeatures00002', std_10d_5d_rolling=2)

In [3]: features.set_mean_to_std_10d_5d_rolling()
update

In [4]: features
Out[4]: SpreadFeatures(mean_10d_5d_rolling=1, mean_to_std_10d_5d_rolling=0.5, name='SpreadFeatures00002', std_10d_5d_rolling=2)

In [5]: features.std_10d_5d_rolling=3
update

In [6]: features
Out[6]: SpreadFeatures(mean_10d_5d_rolling=1, mean_to_std_10d_5d_rolling=0.3333333333333333, name='SpreadFeatures00002', std_10d_5d_rolling=3)

In [7]: features.std_10d_5d_rolling=3
update

In [8]: features
Out[8]: SpreadFeatures(mean_10d_5d_rolling=1, mean_to_std_10d_5d_rolling=0.3333333333333333, name='SpreadFeatures00002', std_10d_5d_rolling=3)

In [9]: import panel as pn

In [10]: pn.Param(features).show()
WARNING:tornado.access:404 GET /favicon.ico (::1) 0.99ms

image

I.e. the “setters” are not run when I construct the object. But only when I use the object after construction.

Is there a “param” way of specifying that events should be triggered during construction or do I need to override the parent constructor and implement this my self?

Is there a better way of achieving what I wan’t to do here?

This code achieves what I wan’t but I would still like to know if I’m approaching this right?

import param


class SpreadFeatures(param.Parameterized):
    """A class containing features of a spread"""

    mean_10d_5d_rolling = param.Number()
    std_10d_5d_rolling = param.Number()

    mean_to_std_10d_5d_rolling = param.Number(None, constant=True)

    def __init__(self, **params):
        super().__init__(**params)

        self.set_mean_to_std_10d_5d_rolling()

    @param.depends("mean_10d_5d_rolling", "std_10d_5d_rolling", watch=True)
    def set_mean_to_std_10d_5d_rolling(self):
        print("update")
        with param.edit_constant(self):
            if self.std_10d_5d_rolling:
                self.mean_to_std_10d_5d_rolling = self.mean_10d_5d_rolling / self.std_10d_5d_rolling
            else:
                self.mean_to_std_10d_5d_rolling = None


features = SpreadFeatures(mean_10d_5d_rolling=1, std_10d_5d_rolling=2)
display(features)
In [1]: %run scripts\issues\issue_param_constructor.py
update
SpreadFeatures(mean_10d_5d_rolling=1, mean_to_std_10d_5d_rolling=0.5, name='SpreadFeatures00003', std_10d_5d_rolling=2)