holoviz / panel

Panel: The powerful data exploration & web app framework for Python
https://panel.holoviz.org
BSD 3-Clause "New" or "Revised" License
4.6k stars 499 forks source link

Future of `pn.interact`? #6729

Open maximlt opened 4 months ago

maximlt commented 4 months ago

@Azaya89 has been working on modernizing the Attractors example on examples.holoviz.org. One of its notebooks makes use of pn.interact.

pn.interact has been soft-deprecated in Panel:

I wanted to start a discussion to see:

  1. Whether pn.interact should really be deprecated,
  2. If so, how exactly? Is removing it from the toctree enough?

The work done by @Azaya89 has forced us to think about how to replace pn.interact with other Panel constructs. The example starts by defining the function that is meant to be interacted with and a dict ps of colormaps meant to be passed to the function.

from colorcet import palette_n
ps ={k:p[::-1] for k,p in palette_n.items()}

def clifford_plot(a=1.9, b=1.9, c=1.9, d=0.8, n=1000000, colormap=ps['kbc']):
    cvs = ds.Canvas(plot_width=600, plot_height=600)
    xs, ys = clifford_trajectory(a, b, c, d, 0, 0, n)
    agg = cvs.points(pd.DataFrame({'x':xs, 'y':ys}), 'x', 'y')
    return tf.shade(agg, cmap=colormap)

And it just calls pn.interact.

pn.interact(clifford_plot, n=(1,20000000), colormap=ps)

Which immediately generates this app: image

The suggested alternative implementation to pn.interact(...) is as follows.

widgets = {
    'a': pn.widgets.FloatSlider(value=1.9, end=2.0, step=0.1, name='a'),
    'b': pn.widgets.FloatSlider(value=1.9, end=2.0, step=0.1, name='b'),
    'c': pn.widgets.FloatSlider(value=1.9, end=2.0, step=0.1, name='c'),
    'd': pn.widgets.FloatSlider(value=0.8, end=1.0, step=0.1, name='d'),
    'n': pn.widgets.IntSlider(value=10000000, start=1000, end=20000000, step=100, name='n'),
    'colormap': pn.widgets.Select(value=ps['bmw'], options=ps, name='colormap'),
}

attr = pn.bind(clifford_plot, **widgets)
pn.Column(*widgets.values(), attr)

With this approach, the widgets must all explicitly be defined, pn.bind is used to create a bound function, and pn.Column is used to layout the widgets and the output of the bound function.

While this isn't too complicated Panel code, I think it's still a pretty big gap for users who aren't too familiar with Panel or with Python in general, users that could easily get started with pn.interact. To understand/read the code above they need to know about:

Of course, the dictionary creation and unpacking could be replaced by a more explicit and simpler approach, at the expense of making the code longer.

So I wonder:

MarcSkovMadsen commented 4 months ago

Some of the arguments for deprecating pn.interact.

ahuang11 commented 4 months ago

Would it make sense to make pn.Param wrap this or pn.ParamFunction?

Related: https://github.com/holoviz/panel/issues/6464

philippjfr commented 4 months ago

Would it make sense to make pn.Param wrap this or pn.ParamFunction?

Could you explain what you're suggesting.

ahuang11 commented 4 months ago

Functionality of pn.interact migrated to pn.Param so you can pass Param classes, Python functions (where the params are auto determined by type and positional args), widgets, etc.

import panel as pn
pn.extension()
button = pn.widgets.Button(name='Click me')

def a_function(x: int, y: int, z:int = 10):
    return x + y + z

pn.Param([a_function, button], parameters=["x", "y", "clicks"], show_name=False)

Or, on second thought, I don't really know what combining multiple objects will do, so actually maybe not combining them, but just be able to wrap a function.

import panel as pn
pn.extension()

def a_function(x: int, y: int, z:int = 10):
    return x + y + z

pn.Param(a_function, parameters=["x", "y"], show_name=False)
philippjfr commented 4 months ago

Thanks for making that proposal explicit. That said, I'm -100 on overloading pn.Param like that 😃

philippjfr commented 4 months ago

Sorry I guess I should have considered ParamFunction too. There I could sort of see it but still think it could be quite unexpected.

Coderambling commented 4 months ago

For what it's worth, I find Maxim's alternative with pn.bind easier to understand than the pn.interact code. I guess it is less elegant and more verbose though.

jbednar commented 4 months ago

For completeness, we discussed two other alternatives. Possible today (though untested):

from panel.widgets.widget import widget as w

widgets = dict(a=w(1.9), b=w(1.9), c=w(1.9), d=w(0.8),
               n=pn.widgets.IntSlider(default=1000000, range=(1,20000000)),
               colormap=w(ps['kbc']))

pn.bind(clifford_plot, **widgets)

(shorter than explicitly listing all widgets, but more mysterious, and requires a lot of explanation). Or, if we add a function to inspect the function signature the way interact does, but use bind (or .rx):

widgets = pn.widgets(clifford_plot, n=(1,20000000), colormap=ps)
pn.Column(pn.bind(clifford_plot, **widgets), widgets)

Still pretty obscure, and not easy to see how to motify it, but at least avoids adding a fundamentally new API like interact.

I guess one final option: keep interact, but explicitly turn it into a macro, built up out of other calls like shown here. Then if we introduce it, we can say that it's nothing special, just a handy wrapper that's useful in a notebook or command line. Today we can't say that, but I don't think it's a major job to rewrite it using separate functions for synthesizing widgets from values (already useful for pn.rx) plus pn.bind or pn.rx. That might be my vote unless someone comes up with something better!

Coderambling commented 4 months ago

Hm. I don't have a complete picture of this, but from my perspective, there are already so many parts to Panel that soft-deprecating something (in combination with the macro approach?) seems to be a positive thing. As long as there are good alternatives of course.

jbednar commented 4 months ago

Definitely. I think we all agree that we don't think pn.interact fits in, and we do want to reduce the visible API surface of panel, particularly for new users. But we don't seem to have come up with a very good alternative to pn.interact. :-(