holoviz / param

Param: Make your Python code clearer and more reliable by declaring Parameters
https://param.holoviz.org
BSD 3-Clause "New" or "Revised" License
433 stars 74 forks source link

Dependencies on a method name is not invoking #731

Open david6l34 opened 1 year ago

david6l34 commented 1 year ago

ALL software version info

param.version : 1.13.0

Description of expected behavior and the observed behavior

Following the tutorial in the official reference https://pyviz-dev.github.io/param/user_guide/Dependencies_and_Watchers.html#dependency-specs Under dependency specs, it is stated that a method can depends on another method by another method's name.

However, cb4 does not execute after invoking cb3

Complete, minimal, self-contained example code that reproduces the issue

# code goes here between backticks
import param

class C(param.Parameterized):
    _countries = {'Africa': ['Ghana', 'Togo', 'South Africa'],
                  'Asia'  : ['China', 'Thailand', 'Japan', 'Singapore'],
                  'Europe': ['Austria', 'Bulgaria', 'Greece', 'Switzerland']}

    continent = param.Selector(list(_countries.keys()), default='Asia')
    country = param.Selector(_countries['Asia'])

    @param.depends('continent', watch=True)
    def _update_countries(self):
        countries = self._countries[self.continent]
        self.param['country'].objects = countries
        if self.country not in countries:
            self.country = countries[0]

c = C()

class D(param.Parameterized):
    x = param.Number(7)
    s = param.String("never")
    i = param.Integer(-5)
    o = param.Selector(['red', 'green', 'blue'])
    n = param.ClassSelector(param.Parameterized, c, instantiate=False)                    

    @param.depends('x', 's', 'n.country', 's:constant', watch=True)
    def cb1(self):
        print(f"cb1 x={self.x} s={self.s} "
              f"param.s.constant={self.param.s.constant} n.country={self.n.country}")

    @param.depends('n.param', watch=True)
    def cb2(self):
        print(f"cb2 n={self.n}")

    @param.depends('x', 'i', watch=True)
    def cb3(self):
        print(f"cb3 x={self.x} i={self.i}")

    @param.depends('cb3', watch=True)
    def cb4(self):
        print(f"cb4 x={self.x} i={self.i}")

d = D()
d

Stack traceback and/or browser JavaScript console output

Screenshots or screencasts of the bug in action

image

Changing d.x works as expected but calling d.cb3 does not invoke d.cb4

maximlt commented 1 year ago

This is not exactly what the doc says, as it adds that:

which means that it will now watch everything that method watches, and will then get invoked after that method is invoked

And this is exactly what happens in the code cell 128 above, cb4 has been marked to watch everything that cb3 depends on and is executed after cb3 is executed.

maximlt commented 1 year ago

Do you think the docs could be improved? Or do you see that as a feature request?

jbednar commented 1 year ago

I agree that it is reasonable to expect that the method would be invoked in this case, but the documentation already seems clear enough that it won't be. I'd favor closing this unless there is a clear proposal for clarifying the docs. Implementing the behavior requested would also be reasonable, but would be a clear change in semantics that seems dangerous to make since it's been like this for so long.