<jupyter_start><jupyter_text>Outputs you can update by nameThis 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:<jupyter_code>print("typical output")<jupyter_output>typical output<jupyter_text>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 stuffYou 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*.<jupyter_code>h1 = display('initial display', display_id='some_destination')<jupyter_output><empty_output><jupyter_text>Ok, so far, nothing earth shattering. But what happens if you call display with the same `display_id` again?<jupyter_code>h2 = display('spoiler alert: output updated in both', display_id='some_destination')<jupyter_output><empty_output><jupyter_text>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:<jupyter_code>h3 = display('no output here, update above', display_id='some_destination', update=True)<jupyter_output><empty_output><jupyter_text>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!<jupyter_code>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()<jupyter_output><empty_output><jupyter_text>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.<jupyter_code>bar.progress = 50
bar.update()<jupyter_output><empty_output><jupyter_text>Now go half-way again<jupyter_code>bar.progress = 75
bar.update()<jupyter_output><empty_output><jupyter_text>Our original bar is kind of far away now, let's get another view of it below.<jupyter_code>bar.display()<jupyter_output><empty_output><jupyter_text>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 *progress*ive. We subclass `ProgressBar` and now we make `progress` into a [Python property](https://docs.python.org/3/library/functions.htmlproperty), 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`.<jupyter_code>class AutoupdatingProgressBar(ProgressBar):
    @property
    def progress(self):
        return self._progress
    
    @progress.setter
    def progress(self, value):
        self._progress = value
        self.update()
better_bar = AutoupdatingProgressBar(100)
better_bar.display()
better_bar.progress = 40<jupyter_output><empty_output><jupyter_text>Much better. No more pesky `update` calls. Let's make a little animation that [Zeno](https://en.wikipedia.org/wiki/Zeno%27s_paradoxesDichotomy_paradox) would be proud of:<jupyter_code>import time
better_bar.progress = 0
for _ in range(10):
    time.sleep(.5)
    better_bar.progress += (better_bar.capacity - better_bar.progress) / 2<jupyter_output><empty_output><jupyter_text>You might have noticed that each `ProgressBar` autogenerates a random `display_id` which is handy if you want to have several of them.<jupyter_code>num_bars = 5
bars = [AutoupdatingProgressBar(100) for _ in range(num_bars)]
for b in bars:
    b.display()
import random 
for x in range(40):
    time.sleep(.1)
    idx = random.randrange(num_bars)
    bars[idx].progress +=  random.randint(-2, 10)
for b in bars:
    b.display()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09383202099737532, 'fraction_numerical': 0.008967629046369203, 'mean_word_length': 5.105473965287049, 'pattern_counts': {'":': 0, '<': 46, '<?xml version=': 0, '>': 48, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '11', 'dir': 'jupyter-structured-clean-dedup', 'id': '405797', 'license': 'BSD-3-Clause', 'n_tokens_mistral': 1415, 'n_tokens_neox': 1360, 'n_words': 469, 'path': 'python/display-updates.ipynb', 'repo_name': 'choldgraf/examples', 'size': '4572'}