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

Add pn.panelize or at least improvements to pn.depends #638

Closed
jsignell opened this issue Sep 9, 2019 · 8 comments · Fixed by #639
Closed

Add pn.panelize or at least improvements to pn.depends #638

jsignell opened this issue Sep 9, 2019 · 8 comments · Fixed by #639
Labels
TRIAGE Default label for untriaged issues

Comments

@jsignell
Copy link
Member

jsignell commented Sep 9, 2019

Background

I have been playing around with the stock APIs example which is totally great and has really been helping me understand the various APIs.

In the course of my experiments I was surprised that while you can do:

def get_plot(ticker, window_size):
    df = get_df(ticker, window_size)
    return df.hvplot.line('date', 'close', grid=True, width=600)

pn.interact(get_plot, ticker=tickers, window_size=(1, 21, 5))

When working with depends the syntax becomes:

ticker = pn.widgets.Select(name='Ticker', options=tickers)
window = pn.widgets.IntSlider(name='Window Size', value=6, start=1, end=21)

pn.depends(ticker=ticker.param.value, window_size=window.param.value)(get_plot))

This is just one example of how the interact and depends methods are different in kind of strange and surprising way. I know that these methods have been discussed at length, but it seems worth ironing out a little more since these are the first places that users get stuck when first starting out with panel.

Proposal 1 (less ambitious)

To make depends more user-friendly, pn.depends should:

  1. accept a function or class method as the first argument, so that the syntax is the same as pn.interact
  2. accept widgets in addition to parametrized values.
  3. return a panel object (as if you had done pn.Pane(get_plot))

Example 1

PROPOSED:

pn.depends(get_plot, ticker=ticker, window_size=window)

CURRENT:

pn.depends(ticker=ticker.param.value, window_size=window.param.value)(get_plot)

Example 2

PROPOSED:

ticker = pn.widgets.Select(name='Ticker', options=tickers)
window = pn.widgets.IntSlider(name='Window Size', value=6, start=1, end=21)

@pn.depends(ticker=ticker, window_size=window)
def get_plot(ticker, window_size):
    df = get_df(ticker, window_size)
    return df.hvplot.line('date', 'close', grid=True, width=600)

get_plot

CURRENT:

ticker = pn.widgets.Select(name='Ticker', options=tickers)
window = pn.widgets.IntSlider(name='Window Size', value=6, start=1, end=21)

@pn.depends(ticker=ticker.param.value, window_size=window.param.value)
def plot(ticker, window_size):
    df = get_df(ticker, window_size)
    return df.hvplot.line('date', 'close', grid=True, width=600)

pn.Pane(plot)

Proposal 2 (more ambitious)

I'd like for there to be a combined decorator (perhaps called pn.panelize) that would act like pn.interact or like pn.depends based on what the inputs to the decorator are.

Example 1 (maps to interact)

@pn.panelize(ticker=tickers, window_size=(1, 21, 5))
def get_plot(ticker, window_size):
    df = get_df(ticker, window_size)
    return df.hvplot.line('date', 'close', grid=True, width=600)

get_plot

Example 2 (maps to depends)

ticker = pn.widgets.Select(name='Ticker', options=tickers)
window = pn.widgets.IntSlider(name='Window Size', value=6, start=1, end=21)

@pn.panelize(ticker=ticker, window_size=window)
def get_plot(ticker, window_size):
    df = get_df(ticker, window_size)
    return df.hvplot.line('date', 'close', grid=True, width=600)

get_plot
@jsignell jsignell added the TRIAGE Default label for untriaged issues label Sep 9, 2019
@jsignell
Copy link
Member Author

jsignell commented Sep 9, 2019

One wrinkle I can see is that the depends one should not return the widgets whereas the the interact one should. So that might feel unexpected to the user.

@jbednar
Copy link
Member

jbednar commented Sep 9, 2019

For proposal 1:

To make depends more user-friendly, pn.depends should:

  1. accept a function or class method as the first argument, so that the syntax is the same as pn.interact

Do we ever show something like pn.depends(ticker=ticker.param.value, window_size=window.param.value)(get_plot)) in the docs? pn.depends is normally used as a decorator, right? Of course it can be called directly, but as far as I know the intent was purely for it to be used with the decorator syntax, and I think it will confuse users to use it in other ways. People are already uncomfortable enough with decorators used in the typical way!

  1. accept widgets in addition to parametrized values.

If that's practical, sounds great! I'd be very happy to get rid of the extra syntax and leave it easier for people to remember. Note that what they currently accept is not actually a parameterized value (i.e. not the value of a parameter), but the Parameter object named "value" on the widget. So really the proposal is to accept widgets in addition to Parameter objects, automatically finding the "value" Parameter object of the widget and substituting that. Seems like a good idea to me.

  1. return a panel object (as if you had done pn.Pane(get_plot))

Doesn't that conflict with the semantics of decorators? Normally a decorator applied to a function will leave that function defined in the namespace, but replaced with a wrapper function. With the above proposal, any subsequent call to get_plot as a function will fail, right?

The issues with 1 and 3 here suggest to me that you maybe want something other than @pn.depends, i.e. some other call that behaves the way that you like, and is not a decorator?

@jbednar
Copy link
Member

jbednar commented Sep 9, 2019

For proposal 2, I'm not quite sure what the plan would be, but I don't think it works, because in both cases if panelize is a decorator then get_plot should be a function (as a decorated function should still be a function), not a set of widgets or a panel. It seems to me that you don't want a decorator, just a regular function, which seems easy enough to implement to me:

pn.panelize(get_plot, ticker=tickers, window_size=(1, 21, 5))
pn.panelize(get_plot, ticker=ticker, window_size=window)

@jsignell
Copy link
Member Author

jsignell commented Sep 9, 2019

That is an interesting point that decorators shouldn't alter the type of the inner function. This is not the case in the current version of pn.interact:

Screen Shot 2019-09-09 at 3 48 41 PM

@jbednar
Copy link
Member

jbednar commented Sep 9, 2019

For interact we are explicitly copying the interface from ipywidgets, but for pn.depends we are following standard usage of decorators. In ipywidgets the decorator usage is an extra bonus (it's mainly meant as a direct call), but in panel pn.depends is intended entirely (or almost entirely) as a decorator. So I think pn.depends() should work like a normal decorator, without any shenanigans like returning a non-function, and we should leave any other behavior to other functions. @philippjfr would be the one to make the call, really, though.

@jsignell
Copy link
Member Author

jsignell commented Sep 9, 2019

Ok so maybe an in-between would be to allow interact to accept widgets and param.values and allow depends to also accept widgets.

@jbednar
Copy link
Member

jbednar commented Sep 9, 2019

I'd be very happy to see that!

@jsignell
Copy link
Member Author

jsignell commented Sep 9, 2019

Here is a WIP implementation of panelize anyways.

def panelize(func, **kwargs):
    parametrized = True
    for k, v in kwargs.items():
        if isinstance(v, pn.param.param.parameterized.Parameter):
            pass
        elif isinstance(v, pn.widgets.base.Widget):
            kwargs[k] = v.param.value
        else:
            parametrized = False
            continue
    if parametrized:
        return pn.Pane(pn.depends(**kwargs)(func))
    return pn.interact(func, **kwargs)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
TRIAGE Default label for untriaged issues
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants