What's a better pattern for displaying expensive to compute elements

I noticed a common pattern in my panel code:

from time import sleep

import numpy as np
import panel as pn
from matplotlib.figure import Figure

pn.extension(sizing_mode="stretch_width")

button = pn.widgets.Button(name="Click me")
select = pn.widgets.Select(name="Select")
layout = pn.Column()


def expensive_computation(s):
    sleep(1)
    print("expensive!")
    return pn.Column(pn.pane.Str(f"Selected: {s}"))


def expensive_2_step(s):
    layout.objects = [expensive_computation(s)]


pn.bind(expensive_2_step, select, watch=True)
select.options = ["a", "b", "c"]


def display(event):
    fig = Figure()
    ax = fig.add_subplot(111)
    ax.plot(np.random.rand(10))
    return pn.Column(select, pn.bind(lambda _: layout, select), fig)


pn.serve(
    pn.Column(
        button,
        pn.bind(display, button),
    )
)
    button,
        pn.bind(display, button),
    )
)

breakdown: clicking the button will refresh the whole layout

if you replace pn.bind(lambda _: layout, select), fig) with pn.bind(expensive_computation, select), fig) it will indeed perform the calculation on every redraw.

Is there a better pattern that still involves only panel components? Could panel do something in the future to implement this boilerplate for us in the background?

Would caching suffice for your use case?
https://panel.holoviz.org/how_to/caching/memoization.html

pn.cache is supposed to cache data. Simply adding pn.cache to

expensive_2_step

will break the example

Try this pattern – I’m using append to build up the Column:

from time import sleep

import numpy as np
import panel as pn

pn.extension(sizing_mode="stretch_width")

button = pn.widgets.Button(name="Click me")
select = pn.widgets.Select(name="Select", options=['a', 'b', 'c'])
layout = pn.Column()

def expensive_computation(s):
    sleep(1)
    print("expensive!")
    layout.append(pn.pane.Str(f"Selected: {s}"))
    return layout

pn.serve(pn.Column(button, select, pn.bind(expensive_computation, s=select)))

I don’t exactly understand how this solves the issue. The original example replaces the objects of the layout instead of appending (which is wanted). Could you replicate the button’s functionality also in this example? The goal would be to be able to click the button and not re-trigger the expensive computation unnecesarily

to clarify:

what I want is to have the behavior of the original code posted in this thread, with the simplicity of writing:

from time import sleep

import numpy as np
import panel as pn
from matplotlib.figure import Figure

pn.extension(sizing_mode="stretch_width")

button = pn.widgets.Button(name="Click me")
select = pn.widgets.Select(name="Select", options=["a", "b", "c"])
layout = pn.Column()


def expensive_computation(s):
    sleep(1)
    print("expensive!")
    return pn.Column(pn.pane.Str(f"Selected: {s}"))


def display(event):
    fig = Figure()
    ax = fig.add_subplot(111)
    ax.plot(np.random.rand(10))
    return pn.Column(select, pn.bind(expensive_computation, select), fig)


pn.serve(
    pn.Column(
        button,
        pn.bind(display, button),
    )
)