param
param copied to clipboard
pm.depends(pn.state.param.busy) on a method
ALL software version info
- param 1.10.1
- panel 0.11.3
- python 3.7.10
Description of expected behavior and the observed behavior
I'm trying to use @pm.depends(pn.state.param.busy, watch=True) on an instance method of a class. When I modify a parameter, I expect the method to get called once with argument busy=True and once with argument busy=False.
Instead I'm seeing potentially 2 issues:
- Whenever I use
@pm.depends(pn.state.param.busy, watch=True), even on a normal function, the function or method gets called 4 times, with arguments busy = True/False/True/False. It doesn't seem to be due to thewatchargument because when I remove it nothing gets called. - In case of a method, every call gets duplicated (for a total of 8) and in half the calls the
selfargument is the booleanbusy, in the other half it's the expected object instance. I never get both in the same call.
See example code and output below.
Complete, minimal, self-contained example code that reproduces the issue
import panel as pn
import param as pm
from bokeh.server.server import Server
class A(pm.Parameterized):
value = pm.Integer()
def __init__(self):
super().__init__()
self.a = 1234
@pm.depends(pn.state.param.busy, watch=True)
def indicator_method(self, *args, **kwargs):
print("method", self, args, kwargs)
# @pm.depends(pn.state.param.busy, watch=True)
# def indicator_func(*args, **kwargs):
# print("func", args, kwargs)
def bkapp(doc):
a = A()
p = pn.Row(a.param)
doc.add_root(p.get_root())
if __name__ == '__main__':
port = 8901
server = Server({'/': bkapp}, num_procs=1, port=port)
server.start()
server.io_loop.add_callback(server.show, "/")
server.io_loop.start()
Output when changing value
Example as-is:
method True () {}
method <A A00097> () {}
method False () {}
method <A A00097> () {}
method True () {}
method <A A00097> () {}
method False () {}
method <A A00097> () {}
When commenting out the method and uncommenting the function:
func (True,) {}
func (False,) {}
func (True,) {}
func (False,) {}
Workaround for method vs function
Below two potential workarounds. The method still gets called 4 times though.
class A(pm.Parameterized):
value = pm.Integer()
def __init__(self):
super().__init__()
self.a = 1234
# Workaround 1
f = functools.partial(self.indicator.__func__, self)
pm.depends(pn.state.param.busy, watch=True)(f)
# # Workaround 2
# pn.state.param.watch(self.indicator, 'busy')
def indicator(self, busy):
print(self.a, busy)
Output (workaround 1):
1234 True
1234 False
1234 True
1234 False
Output (workaround 2):
1234 Event(what='value', name='busy', obj=state(servers=[]), cls=<class 'panel.io.state._state'>, old=False, new=True, type='changed')
1234 Event(what='value', name='busy', obj=state(servers=[]), cls=<class 'panel.io.state._state'>, old=True, new=False, type='changed')
1234 Event(what='value', name='busy', obj=state(servers=[]), cls=<class 'panel.io.state._state'>, old=False, new=True, type='changed')
1234 Event(what='value', name='busy', obj=state(servers=[]), cls=<class 'panel.io.state._state'>, old=True, new=False, type='changed')
I'm using workaround #1 in my real application and somehow it's only calling the indicator twice (busy = True, False). Not really sure what the difference is, I have 2 methods that each depend on the same 4 parameters defined on the class. Good news though 🙂
Trying to simplify the original example by removing Panel related code.
import param
class Other(param.Parameterized):
busy = param.Boolean(default=False)
other = Other()
class A(param.Parameterized):
@param.depends(other.param.busy, watch=True)
def debug(self, *args, **kwargs):
print(f"debug {self=} {args=} {kwargs=}")
a = A()
other.busy = True
Output:
debug self=A(name='A00026') args=() kwargs={}
debug self=True args=() kwargs={}
So effectively the debug method is called twice, the second call being weird as it's calling debug as if it was a simple function passing True to self.
other._param_watchers['busy']['value'] contains:
[Watcher(inst=Other(busy=True, name='Other00025'), cls=<class '__main__.Other'>, fn=<function depends.<locals>.cb at 0x110770b80>, mode='args', onlychanged=True, parameter_names=('busy',), what='value', queued=False, precedence=0),
Watcher(inst=Other(busy=True, name='Other00025'), cls=<class '__main__.Other'>, fn=<function _m_caller.<locals>.caller at 0x11081a8b0>, mode='args', onlychanged=True, parameter_names=('busy',), what='value', queued=False, precedence=-1)]