Compute parameters at initialization but keep them constant afterward

So my scenario is the following: I would like my parameterized class to have a parameter that is set during initialization, and never changed again. constant=True seems to do the trick except I can’t dynamically set the parameter during the initialization:

class T(param.Parameterized):
    c = param.Parameter(default=None, constant=True)
    d = param.Parameter(constant=False)
    
    def __init__(self, *argv, **kwarg):
        super().__init__(*argv, **kwarg)
        if self.c is None:
            self.c = self.d*2 # TypeError: Constant parameter 'c' cannot be modified 
    
t = T(d=123)

A workaround could be using a context manager, but this seems overkill, see below. Is there any way param can accommodate the desired behavior, possibly by another kwarg on param.Parameter?

Thanks!

class ModifyConstant:
    def __init__(self, instance, name):
        self.instance = instance
        self.name = name
        if not getattr(instance.param, name).constant:
            instance.param.warning('Modifying a parameter with a mechanism intended for constants')
        
    def __enter__(self):
        self._set_constant(self.instance, self.name, False)
        return self
        
    def __exit__(self, *exc):
        self._set_constant(self.instance, self.name, True)
    
    @staticmethod
    def _set_constant(instance, name, val):
        getattr(instance.param, name).constant = val

class T(param.Parameterized):
    c = param.Parameter(constant=True)
    d = param.Parameter(constant=False)
    
    def __init__(self, *argv, **kwarg):
        super().__init__(*argv, **kwarg)
        if self.c is None:
            with ModifyConstant(self, 'c'):
                self.c = self.d*2+1
    
t = T(c=None, d=123)

t

PS: The param code also seems to indicate that parameters can be computed/updated on the fly, using hooks. I never managed to figure out how that would work, but maybe that would address the problem here?

Hi!

The Dynamic param type allows its default value to be a callable object (a class, a lambda, a function) that is gonna be called when you try to get access to the attribute value. Not sure though that it’s enough for your need, it might be tricky to define a function that has access to the other parameter values (e.g. d from T(d=123)). Note that param already has a context manager to edit constant values, it’s called edit_constant and you can find its source here, I haven’t tried it though.

Cheers!

1 Like

Hi @poplarshift

Below are two ways I normally achieve setting a constant. Hope it helps?

import param

class T1(param.Parameterized):
    c = param.Parameter(default=None, constant=True)
    d = param.Parameter(constant=False)

    def __init__(self, *argv, **kwarg):
        if "c" not in kwarg and "d" in kwarg:
            kwarg["c"]=2*kwarg["d"]

        super().__init__(*argv, **kwarg)

t1 = T1(d=111)
print("t1.c", t1.c)

class T2(param.Parameterized):
    c = param.Parameter(default=None, constant=True)
    d = param.Parameter(constant=False)

    def __init__(self, *argv, **kwarg):
        super().__init__(*argv, **kwarg)

        if self.c is None:
            with param.edit_constant(self):
                self.c = self.d*2

t2 = T2(d=222)
print("t2.c", t2.c)
$ python example.py
t1.c 222
t2.c 444
2 Likes

Thanks you two, that’s perfect! Oh well if anything I refreshed my knowledge on writing context managers :slight_smile:

1 Like