How do I clean up unused panels in a Jupyter notebook?

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.

Interesting. Out of curiosity, why did you put ‘free’ in quotes? Are there hidden fees?

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.

1 Like

Hmm, maybe this is actually more fundamental to Param, since it looks similar to this problem from a year ago: How to destroy Parameterized object with watchers and depends decorator.

1 Like

I don’t think its possible to clean this up. I would recommend one of the below

  • report it as a bug with a minimum, reproducible example on the param github
  • adding a feature request on the param github for adding functionality to do the clean up if at all possible.
  • adding a feature request on the param github to document how to best handle this situation.

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:

  1. You can simply add a pn.cache decorator around the view method:
    @pn.cache
    @param.depends('a')
    def view(self):
        print('view got called')
        return str(self.a)

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.

1 Like