Reputation: 57261
I have a class that uses IPython widgets to render rich output. This output changes over time. I manage this by adding a callback to the IOLoop that alters the output periodically.
class Foo(object):
def _ipython_display_(self, **kwargs):
html = HTML("<h2>hello</h2>")
def f():
html.value = ... # TODO: replace with weakrefs to avoid cycle
from tornado.ioloop import PeriodicCallback
html._pc = PeriodicCallback(f, 1000)
html._pc.start()
return html._ipython_display_(**kwargs)
However, I also want this PeriodicCallback to stop once html
is no longer visible on the screen. Normally I would use __del__
or finalize
or something, but it appears that the html
object will likely be kept in the notebook's output history (variables like _10
) and so my periodic callback survives forever.
Is there any way to get a signal when a displayable element is no longer displayed?
Upvotes: 0
Views: 391
Reputation: 1484
As you may know, widgets on the back end (the IPython kernel) are backed by a single object, while at the front end (the browser) it will have a model, and zero to N views.
Every time you display a widget, a new view is created. Since ipywidgets 7 it is possible to keep track of the number of views on the front end (the browser), by setting (on widget construction) the '_view_count' trait to 0, however, beware of what the help string says:
EXPERIMENTAL: The number of views of the model displayed in the frontend. This attribute is experimental and may change or be removed in the future. None signifies that views will not be tracked. Set this to 0 to start tracking view creation/deletion. The default is None, which will make it not track the view count (since for many widgets it will lead to a lot of traffic going around).
Assuming you are in the Jupyter notebook:
import ipywidgets
slider = ipywidgets.FloatSlider(value=2, _view_count=0)
assert slider._view_count == 0
For the next cell, you display it:
slider
And in the next cell you can check that the view count has gone up
assert slider._view_count == 1
Since this '_view_count' is a trait, you can also listen to changes
# use a output widget to lot lose output
output = ipywidgets.Output()
output
def view_count_changed(change):
with output:
print('View count has changes', change)
slider.observe(view_count_changed, '_view_count')
A more advanced example notebook that might be useful is here, where I make an 'ExpensivePlot' object, that will keep track of the 'dirty' state of the plot, and the _view_count to only update the plot when needed.
PS: This last example of course is not a really expensive plot, but a stripped down version of what I do in vaex when I use the bqplot for plotting, where it can take >1 second to update a plot).
Upvotes: 2