Inactive Tab computes when dynamic=True

For dynamic Tabs, it is not clear what “dynamic” actually means.

Does it mean that the inactive tab components run (are reactive) but are just not sent to the browser?

OR

Does it mean that the inactive tabs (and any computation associated with them) are not executed - saving valuable compute resource?

I’m noticing that it is the first one, but I could be doing something wrong. Is this behavior expected of the dynamic Tabs?

A more concrete example - Each tab takes one second to compute. As soon as I change a widget, I’d like only the active tab to be recomputed, but instead I get the sleep lag from each pane. Is there a way to make the inactive tabs not compute?

import panel as pn
pn.extension()
from time import sleep

sel = pn.widgets.Select(options=['option 1', 'option 2'], value='option 1')

@pn.depends(sel)
def t1(sel):
    print('t1')
    sleep(1)
    return pn.pane.Markdown(sel)
    
@pn.depends(sel.param.value)
def t2(sel):
    print('t2')
    sleep(1)
    return pn.pane.Markdown(sel)

@pn.depends(sel)
def t3(sel):
    print('t3')
    sleep(1)
    return pn.pane.Markdown(sel)

pn.Row(
    sel,
    pn.Tabs(('t1', t1), ('t2', t2), ('t3', t3), dynamic=True),
) 

Could be polished, but something like:

import time
import panel as pn
pn.extension()

placeholder = pn.Column(pn.indicators.LoadingSpinner())

class DynamicTabs():
    def t0(self):
        time.sleep(1)
        return 'Loaded tab 1!'

    def t1(self):
        time.sleep(2)
        return 'Loaded tab 2 in 2 seconds...'

    def t2(self):
        time.sleep(3)
        return 'Wow tab 3 took 3 seconds!'

    def trigger(self, event):
        tab_num = event.new
        event.obj.objects = [
            obj if i != tab_num else getattr(self, f't{tab_num}')()
            for i, obj in enumerate(event.obj.objects)
        ]

    def view(self):
        tabs = pn.Tabs(('t1', placeholder), ('t2', placeholder), ('t3', placeholder))
        tabs.param.watch(self.trigger, 'active')
        return tabs


dynamictabs = DynamicTabs()
tabs = dynamictabs.view()
tabs
1 Like

Thank you @ahuang11! This is great. ​I’ve spent a good while trying to adapt your solution with the added “polish” you mention.

Suppose I have a dashboard-wide widget that gets used in each of the three tabs and so I need to @pn.depends on it. Such as:
sel = pn.widgets.Select(options=['option 1', 'option 2'], value='option 1')

My issue is that I only want to depend on it for the active tab. With the manually defined watchers, I think I have to route the dashboard-wide widget through the trigger too with more “watching”?

Perhaps more important than a comprehensive solution is understanding whether this workaround is expected of end-users or whether the dynamic feature of Tabs has a bug that should be reported in github.

Feel free to report on GitHub! I think it’s useful to dynamically call functions on active.

1 Like