Param with overridden __set__ is not reflected in panel widget

Hi,

So I want to expose settings in my camera to a panel dashboard.
Settings are not always writable, so I want to readback the value from the camera in a set method.
What I find weird is that the widget that tries to set a new value does no longer reflect the value of the parameter. An indicator does keep reflecting it. Here is a minimal example:

import panel as pn
import param


class CameraNumber(param.Number):
    __slots__ = ["camera_parameter"]

    def __init__(self, camera_parameter):
        super().__init__()
        self.camera_parameter = camera_parameter

    def __set__(self, obj, val):
        self.camera_parameter.SetValue(val)
        super().__set__(obj, self.camera_parameter.GetValue())


class CameraParameter:
    value = 0

    def SetValue(self, value):
        self.value = round(value)

    def GetValue(self):
        return self.value


class Camera:
    Gain = CameraParameter()


class A(param.Parameterized):
    camera = Camera()
    gain = CameraNumber(camera.Gain)


a = A()

number = pn.Column(
    pn.widgets.FloatSlider.from_param(a.param.gain),
    pn.indicators.Number.from_param(a.param.gain),
)
number.servable()

I may just be confused, but I wasn’t able to replicate an issue. I ran the code above (adding pn.extension()) , then manually updated a.gain, which was reflected in both the indicator and the widget. There’s probably some other way that I could set the value that would illustrate the problem?

Yes, sorry for not being very specific about the issue.
Here goes:
Try moving the slider before editing the param. For me, when I drag the slider, the rounded value appears in the indicator but not in the slider. If I on the other hand change the value from code before touching the slider, it seems to reflect the param. Something breaks when I touch the slider?

image

Ah, I see. I had noticed the truncation to int, but didn’t read closely enough to realize that was your intended behavior. But I remain deeply confused about what your overall intention is. If the value is to be obtained from elsewhere, why have a widget? If you have a widget, shouldn’t the widget be in charge of the value? Here the widget has an internal parameter value of 0.3, with the gain parameter separately being truncated to 0 as apparently desired, yet you want the value of the gain parameter to round trip back to affecting the widget as opposed to the separate gain parameter?

To me that seems like a recipe for confusion and even having the app lock up or get into a loop, with a user selecting 0.3 and having events propagate that immediately reset it to 0. If I were developing an app like this, I’d try to make a strict separation between read-only values (using indicators or read-only widgets so that values flow in only one direction), and writeable widgets (whose values control something external without reading from it, so that again values flow in only one direction). Handling bidirectional communication of values requires some deep thinking about who is in charge and/or manually handling events using param.watch and related mechanisms, but that sounds like a tricky state-machine logic problem that I prefer to avoid! Maybe someone else can see the issue more clearly than I can, though.

I just threw in that round() for for testing purpose, to show what happens when the widget diverges from the actual value. But sure, maybe it becomes much too complex, the way I envisioned it. Having an indicator + widget could be the way to go. Thank you for your input!

1 Like