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...
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]: