I was surprised to find out that callables supplied to e.g. pn.Row get called once per pane that they feature in when their dependent widgets are modified, but thinking about it this kind of makes sense. What is confusing me now is that they are called even for panes that are no longer being displayed or referred to explicitly. How do I clean these up, so I’m not calling expensive computations many times unnecessarily?
An example:
import param
import panel as pn
pn.extension()
class Foo(param.Parameterized):
a = param.Parameter()
@param.depends('a')
def view(self):
print('view got called')
return str(self.a)
Now in a new cell, run
pn.Row(foo.param, foo.view)
once. When you modify a, ‘view got called’ will be printed. Run the same cell a few more times. Now when you modify a, ‘view got called’ will be printed as many times as the cell has run, even though I’m only viewing the panel once. How do I fix this?
Context: I’m trying to leverage Param and Panel’s ‘free’ interactive visualisation to explore my model simulations. Usually I’m not just running the whole notebook like a script, but experimenting lots with ways of viewing the data, so having to restart the kernel after I’ve made and discarded a few panels is annoying.
Hah, no, I just meant ‘free’ as in ‘buy one get one free’ - it’s free but as an addition to something you had to invest in (time and effort rather than literal money, to be clear). Panel can turn classes written with Param into interactive visualisations relatively easily, but you do have to do the upfront work of (re)writing the class with Param in the first place, which is not entirely straightforward.
The problem here is that IPython (which Jupyter notebook is built on) keeps references to all the instances of pn.Row(foo.param, foo.view) you created (look at _<cell-number> variables in the notebook to confirm this and look at the IPython docs). Since they are never destroyed Panel will correctly update all existing instances of the so called pn.param.ParamMethod that is created to dynamically update when the dependencies of foo.view change.
There are a few options forward here:
You can simply add a pn.cache decorator around the view method:
This will automatically cache the output of the method so it doesn’t have to be called multiple times. This is of course merely a workaround.
So let’s brainstorm some other options, if we disabled the output caching with:
get_ipython().cache_size = 0
or simply reset the cache with:
%reset
That unfortunately still does not solve our issue. What we would probably need here is some way to destroy all watchers that were created on each instantiation of the pn.param.Param and pn.param.ParamMethod objects (generated each time you ran pn.Row(foo.param, foo.view)). This is currently not easy but probably should be. So I’d encourage you to file an issue titled something like "Destroy all watchers associated with a object" on the Panel issue tracker.