graceful interrupt of while loop in ipython notebook
Asked Answered
W

2

11

I'm running some data analysis in ipython notebook. A separate machine collects some data and saves them to a server folder, and my notebook scans this server periodically for new files, and analyzes them.

I do this in a while loop that checks every second for new files. Currently I have set it up to terminate when some number of new files are analyzed. However, I want to instead terminate upon a keypress.

I have tried try-catching a keyboard interrupt, as suggested here: How to kill a while loop with a keystroke?

but it doesn't seem to work with ipython notebook (I am using Windows).

Using openCV's keywait does work for me, but I was wondering if there are alternative methods without having to import opencv.

I have also tried implementing a button widget that interrupts the loop, as such:

from ipywidgets import widgets 
import time
%pylab inline

button = widgets.Button(description='Press to stop')
display(button)

class Mode():
    def __init__(self):
        self.value='running'

mode=Mode()

def on_button_clicked(b):
    mode.value='stopped'

button.on_click(on_button_clicked)

while True:
    time.sleep(1)
    if mode.value=='stopped':
        break

But I see that the loop basically ignores the button presses.

Wiretap answered 17/5, 2016 at 15:48 Comment(1)
For reference, the reason this doesn't work is that the kernel doesn't process the button click until after it's finished running the cell.Boucicault
T
19

You can trigger a KeyboardInterrupt in a Notebook via the menu "Kernel --> Interrupt".

So use this:

try:
    while True:
        do_something()
except KeyboardInterrupt:
    pass

as suggested here and click this menu entry.

Toner answered 17/5, 2016 at 15:58 Comment(1)
KeyboardInterrupt is how the stop button works, though.Annoy
I
0

We need to explicitly poll the UI events

There's a neat little library called jupyter-ui-poll which handles exactly this use case! The rest of the button setup you have can stay the same. We just need to wrap a pollable scope around the loop like so:

from jupyter_ui_poll import ui_events
...
with ui_events() as poll:
    while True:
        time.sleep(1)
        poll(10) # poll queued UI events including button
        if mode.value=='stopped':
            break

The issue is that the IPython kernel executes a queue of events, but there's one queue for both UI event callbacks and cell execution. So once it's busy working on a cell, UI events don't get processed. jupyter-ui-poll temporarily (inside the scope) adjusts this queueing behaviour to allow for explicit polling.

Invaginate answered 3/6, 2023 at 19:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.