holoviz/param

param.watch cannot handle parameters from Parameterized, unlike @param.depends

phi6ias opened this issue · 2 comments

ALL software version info

param 1.12.2

run in powershell prompt using panel serve running Bokeh server 2.4.3 on Tornado 6.2

run in VS Code jupyter notebook
Version: 1.76.2 (user setup)
Commit: ee2b180d582a7f601fa6ecfdad8d9fd269ab1884
Date: 2023-03-14T17:55:54.936Z
Electron: 19.1.11
Chromium: 102.0.5005.196
Node.js: 16.14.2
V8: 10.2.154.26-electron.0
OS: Windows_NT x64 10.0.19045
Sandboxed: No

Description of expected behavior and the observed behavior

When using the decorator @param.depends() it is possible to use a parameter from another class that is stored as a Parameterized instance in the current class, similar as described in this thread --> Holoviz Discourse: Shared Parameter?
However, trying the same with the lower-level self.param.watch() yields an error message.

In the example below, the decorated function works, while the param.watched throws an error on executing the cell comprising anB = Bclass(otherclass=anA). If the "param.watch'ed" function is changed to a decorated, then again everything works fine.

Am I missing something? Should the behaviour not be the same?

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

import param

class Aclass(param.Parameterized):

    Aa = param.Integer(12)
    Ab = param.String("qwe")

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

    def incrAa(self):
        self.Aa += 1

    def moreAb(self):
        self.Ab += self.Ab


class Bclass(param.Parameterized):

    theA = param.Parameterized()
    Ba = param.Integer(3)
    Bb = param.String("asd")

    def __init__(self, otherclass: param.Parameterized, **params):
        self.theA = otherclass
        super().__init__(**params)
        self.param.watch(self.moreAtoB, ['theA.Ab'], queued=True, precedence=1)

    @param.depends('theA.Aa', watch=True)
    def addAtoB(self):
        print("\nBclass - self.theA.Aa: ", self.theA.Aa)

    def moreAtoB(self, *events):
        print("\nBclass - self.theA.Ab: ", self.theA.Ab)


anA = Aclass()
anB = Bclass(otherclass=anA)

anA.incrAa()
anA.moreAb()

Stack traceback and/or browser JavaScript console output

Traceback (most recent call last):
File "c:...\envs...\lib\site-packages\IPython\core\interactiveshell.py", line 3433, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "C:\Users...\AppData\Local\Temp\ipykernel_4172\3905510252.py", line 1, in
anB = Bclass(otherclass=anA)
File "C:\Users...\AppData\Local\Temp\ipykernel_4172\3622346801.py", line 10, in init
self.param.watch(self.moreAtoB, ['theA.Ab'], queued=True, precedence=1)
File "c:...\envs...\lib\site-packages\param\parameterized.py", line 2450, in watch
return self_._watch(fn, parameter_names, what, onlychanged, queued, precedence)
File "c:...\envs...\lib\site-packages\param\parameterized.py", line 2457, in watch
self
._register_watcher('append', watcher, what)
File "c:...\envs...\lib\site-packages\param\parameterized.py", line 2392, in _register_watcher
raise ValueError("%s parameter was not found in list of "
ValueError: theA.Ab parameter was not found in list of parameters of class Bclass

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "c:...\envs...\lib\site-packages\IPython\core\interactiveshell.py", line 2052, in showtraceback
stb = self.InteractiveTB.structured_traceback(
File "c:...\envs...\lib\site-packages\IPython\core\ultratb.py", line 1112, in structured_traceback
return FormattedTB.structured_traceback(
File "c:...\envs...\lib\site-packages\IPython\core\ultratb.py", line 1006, in structured_traceback
return VerboseTB.structured_traceback(
File "c:...\envs...\lib\site-packages\IPython\core\ultratb.py", line 859, in structured_traceback
formatted_exception = self.format_exception_as_a_whole(etype, evalue, etb, number_of_lines_of_context,
File "c:...\envs...\lib\site-packages\IPython\core\ultratb.py", line 812, in format_exception_as_a_whole
frames.append(self.format_record(r))
File "c:...\envs...\lib\site-packages\IPython\core\ultratb.py", line 730, in format_record
result += ''.join(_format_traceback_lines(frame_info.lines, Colors, self.has_colors, lvals))
File "c:...\envs...\lib\site-packages\stack_data\utils.py", line 145, in cached_property_wrapper
value = obj.dict[self.func.name] = self.func(obj)
File "c:...\envs...\lib\site-packages\stack_data\core.py", line 734, in lines
pieces = self.included_pieces
File "c:...\envs...\lib\site-packages\stack_data\utils.py", line 145, in cached_property_wrapper
value = obj.dict[self.func.name] = self.func(obj)
File "c:...\envs...\lib\site-packages\stack_data\core.py", line 677, in included_pieces
scope_pieces = self.scope_pieces
File "c:...\envs...\lib\site-packages\stack_data\utils.py", line 145, in cached_property_wrapper
value = obj.dict[self.func.name] = self.func(obj)
File "c:...\envs...\lib\site-packages\stack_data\core.py", line 617, in scope_pieces
for piece in self.source.pieces
File "c:...\envs...\lib\site-packages\stack_data\utils.py", line 145, in cached_property_wrapper
value = obj.dict[self.func.name] = self.func(obj)
File "c:...\envs...\lib\site-packages\stack_data\core.py", line 106, in pieces
return list(self._clean_pieces())
File "c:...\envs...\lib\site-packages\stack_data\core.py", line 130, in _clean_pieces
raise AssertionError("Pieces mismatches: %s" % mismatches)
AssertionError: Pieces mismatches: [{680, 681}, {696, 695}, {708, 709}, {714, 715}]

Screenshots or screencasts of the bug in action

Thanks for the issue report, I can indeed reproduce it with this simplified version:

import param

class Other(param.Parameterized):
    
    x = param.Integer(1)

other = Other()
    
class P(param.Parameterized):

    other = param.Parameter(other)
    
    def __init__(self, **params):
        super().__init__(**params)
        self.param.watch(self.debug, ['other.x'])
    
    def debug(self, *events):
        print('DEBUG')

p = P()

Traceback:

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[118], line 20
     17     def debug(self, *events):
     18         print('DEBUG')
---> 20 p = P()

Cell In[118], line 15, in P.__init__(self, **params)
     13 def __init__(self, **params):
     14     super().__init__(**params)
---> 15     self.param.watch(self.debug, ['other.x'])

File ~/dev/param/param/parameterized.py:2454, in Parameters.watch(self_, fn, parameter_names, what, onlychanged, queued, precedence)
   2450 if precedence < 0:
   2451     raise ValueError("User-defined watch callbacks must declare "
   2452                      "a positive precedence. Negative precedences "
   2453                      "are reserved for internal Watchers.")
-> 2454 return self_._watch(fn, parameter_names, what, onlychanged, queued, precedence)

File ~/dev/param/param/parameterized.py:2461, in Parameters._watch(self_, fn, parameter_names, what, onlychanged, queued, precedence)
   2457 parameter_names = tuple(parameter_names) if isinstance(parameter_names, list) else (parameter_names,)
   2458 watcher = Watcher(inst=self_.self, cls=self_.cls, fn=fn, mode='args',
   2459                   onlychanged=onlychanged, parameter_names=parameter_names,
   2460                   what=what, queued=queued, precedence=precedence)
-> 2461 self_._register_watcher('append', watcher, what)
   2462 return watcher

File ~/dev/param/param/parameterized.py:2396, in Parameters._register_watcher(self_, action, watcher, what)
   2394 for parameter_name in parameter_names:
   2395     if parameter_name not in self_.cls.param:
-> 2396         raise ValueError("%s parameter was not found in list of "
   2397                          "parameters of class %s" %
   2398                          (parameter_name, self_.cls.__name__))
   2400     if self_.self is not None and what == "value":
   2401         watchers = self_.self._param_watchers

ValueError: other.x parameter was not found in list of parameters of class P

Hi @maximlt

what I did find out in the meantime is that defining the watch on the objects param instead of the class param does work. I don't know if that is the intended behaviour, which is more for you to decide.

import param

class Other(param.Parameterized):
    
    x = param.Integer(1)

other = Other()
    
class P(param.Parameterized):

    other_ = param.Parameter(other)
    
    def __init__(self, **params):
        super().__init__(**params)
        self.other_.param.watch(self.debug, ['x'])
    
    def debug(self, *events):
        print('DEBUG')

p = P()
other.x=2