Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pm.depends(pn.state.param.busy) on a method #500

Open
tomas16 opened this issue Jul 2, 2021 · 2 comments
Open

pm.depends(pn.state.param.busy) on a method #500

tomas16 opened this issue Jul 2, 2021 · 2 comments
Assignees
Labels
type-bug Bug report
Milestone

Comments

@tomas16
Copy link

tomas16 commented Jul 2, 2021

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 the watch argument 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 self argument is the boolean busy, 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')
@tomas16
Copy link
Author

tomas16 commented Jul 2, 2021

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 🙂

@hoxbro hoxbro added this to the v1.12.x milestone Oct 24, 2022
@maximlt
Copy link
Member

maximlt commented Apr 4, 2023

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)]

@maximlt maximlt added the type-bug Bug report label Apr 4, 2023
@maximlt maximlt modified the milestones: v1.12.x, v2.x Apr 4, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type-bug Bug report
Projects
None yet
Development

No branches or pull requests

4 participants