Outputs you can update by name

This notebook demonstrates the new name-based display functionality in the notebook. Previously, notebooks could only attach output to the cell that was currently being executed:


In [1]:
print("typical output")

There was no simple way to make code in one cell to write output to another cell. Now there is!

This feature will only work on IPython >= 5.4, so upgrade if you need to with pip install --upgrade ipython. Feel free to update with

pip install --upgrade ipython

then restart the kernel with Language -> Restart Running Kernel. We'll wait here...

The fun stuff

You made it to the future! Pat yourself on the back and take a deep breath, the scariest part is over. The display function now has an optional display_id parameter. Let's give our next display the boring name and call it some_destination.


In [2]:
h1 = display('initial display', display_id='some_destination')

Ok, so far, nothing earth shattering. But what happens if you call display with the same display_id again?


In [3]:
h2 = display('spoiler alert: output updated in both', display_id='some_destination')

Fantastic! We have a way of mirroring output in multiple places. But what if you only want update the previously named displays, without creating a new one? Just call display with update=True, like this:


In [4]:
h3 = display('no output here, update above', display_id='some_destination', update=True)

Though we have been working with text so far, this also works for the all other output types. Let's make an HTML-based progress bar!


In [5]:
import os
from binascii import hexlify

class ProgressBar(object):
    def __init__(self, capacity):
        self._display_id = hexlify(os.urandom(8)).decode('ascii')
        self.capacity = capacity
        self.progress = 0
        
        
    def _repr_html_(self):
        return "<progress style='width:100%' max='{}' value='{}'></progress>".format(self.capacity, self.progress)
    
    def display(self):
        display(self, display_id=self._display_id)
    
    def update(self):
        display(self, display_id=self._display_id, update=True)

bar = ProgressBar(100)
bar.display()

The progress bar is drawn and it starts off at 0. Fill it up half way and call its update method to get a redraw.


In [6]:
bar.progress = 50
bar.update()

Now go half-way again


In [7]:
bar.progress = 75
bar.update()

Our original bar is kind of far away now, let's get another view of it below.


In [8]:
bar.display()

This is good, but it would be awesome to have a progress bar that would automatically update whenever its progress was modified - that would be truly progressive. We subclass ProgressBar and now we make progress into a Python property, which will allow us to set it and get it like an attribute, but do that using methods. In particular, whenever we assign a new value to progress, we also call update.


In [9]:
class AutoupdatingProgressBar(ProgressBar):
    @property
    def progress(self):
        return self._progress
    
    @progress.setter
    def progress(self, value):
        self._progress = value
        self.update()

In [10]:
better_bar = AutoupdatingProgressBar(100)
better_bar.display()

In [11]:
better_bar.progress = 40

Much better. No more pesky update calls. Let's make a little animation that Zeno would be proud of:


In [12]:
import time
better_bar.progress = 0
for _ in range(10):
    time.sleep(.5)
    better_bar.progress += (better_bar.capacity - better_bar.progress) / 2

You might have noticed that each ProgressBar autogenerates a random display_id which is handy if you want to have several of them.


In [13]:
num_bars = 5
bars = [AutoupdatingProgressBar(100) for _ in range(num_bars)]
for b in bars:
    b.display()

In [14]:
import random 
for x in range(40):
    time.sleep(.1)
    idx = random.randrange(num_bars)
    bars[idx].progress +=  random.randint(-2, 10)

In [15]:
for b in bars:
    b.display()

In [16]: