-
Notifications
You must be signed in to change notification settings - Fork 942
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1361 from jasongrout/async
Add a tutorial for using widgets asynchronously, waiting for widget changes from the frontend
- Loading branch information
Showing
3 changed files
with
268 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,264 @@ | ||
{ | ||
"cells": [ | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": { | ||
"nbsphinx": "hidden" | ||
}, | ||
"source": [ | ||
"[Index](Index.ipynb) - [Back](Widget Custom.ipynb)" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"# Asynchronous widget updates" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"You may want to pause your Python code to wait for an update from a widget. Typically this would be hard to do since running Python code blocks any widget messages from the frontend until the Python code is done.\n", | ||
"\n", | ||
"We'll do this in two approaches: using the event loop integration, and using plain generator functions." | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"## Event loop integration\n", | ||
"\n", | ||
"If we take advantage of the event loop integration IPython offers, we can have a nice solution using the async/await syntax in Python 3." | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"First we register a new event loop integration. This registration will not be needed once https://github.com/ipython/ipykernel/pull/246 is merged and released." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": { | ||
"collapsed": true | ||
}, | ||
"outputs": [], | ||
"source": [ | ||
"import asyncio\n", | ||
"from ipykernel.eventloops import register_integration\n", | ||
"\n", | ||
"@register_integration('asyncio')\n", | ||
"def loop_asyncio(kernel):\n", | ||
" '''Start a kernel with asyncio event loop support.'''\n", | ||
" loop = asyncio.get_event_loop()\n", | ||
"\n", | ||
" def kernel_handler():\n", | ||
" loop.call_soon(kernel.do_one_iteration)\n", | ||
" loop.call_later(kernel._poll_interval, kernel_handler)\n", | ||
"\n", | ||
" loop.call_soon(kernel_handler)\n", | ||
" try:\n", | ||
" if not loop.is_running():\n", | ||
" loop.run_forever()\n", | ||
" finally:\n", | ||
" loop.run_until_complete(loop.shutdown_asyncgens())\n", | ||
" loop.close()" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"Next we invoke our asyncio event loop." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": { | ||
"collapsed": true | ||
}, | ||
"outputs": [], | ||
"source": [ | ||
"%gui asyncio" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"We define a new function that returns a future for when a widget attribute changes." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": { | ||
"collapsed": true | ||
}, | ||
"outputs": [], | ||
"source": [ | ||
"import asyncio\n", | ||
"def wait_for_change(widget, value):\n", | ||
" future = asyncio.Future()\n", | ||
" def getvalue(change):\n", | ||
" # make the new value available\n", | ||
" future.set_result(change.new)\n", | ||
" widget.unobserve(getvalue, value)\n", | ||
" widget.observe(getvalue, value)\n", | ||
" return future" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"And we finally get to our function where we will wait for widget changes. We'll do 10 units of work, and pause after each one until we observe a change in the widget. Notice that the widget's value is available to us, since it is what the `wait_for_change` future has as a result.\n", | ||
"\n", | ||
"Run this function, and change the slider 10 times." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"from ipywidgets import IntSlider\n", | ||
"slider = IntSlider()\n", | ||
"\n", | ||
"async def f():\n", | ||
" for i in range(10):\n", | ||
" print('did work %s'%i)\n", | ||
" x = await wait_for_change(slider, 'value')\n", | ||
" print('async function continued with value %s'%x)\n", | ||
"asyncio.ensure_future(f())\n", | ||
"\n", | ||
"slider" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": { | ||
"collapsed": true | ||
}, | ||
"source": [ | ||
"## Generator approach\n", | ||
"\n", | ||
"If you can't take advantage of the async/await syntax, or you don't want to modify the event loop, you can also do this with generator functions." | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": { | ||
"collapsed": true | ||
}, | ||
"source": [ | ||
"First, we define a decorator which hooks a generator function up to widget change events." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": { | ||
"collapsed": true | ||
}, | ||
"outputs": [], | ||
"source": [ | ||
"from functools import wraps\n", | ||
"def yield_for_change(widget, attribute):\n", | ||
" \"\"\"Pause a generator to wait for a widget change event.\n", | ||
" \n", | ||
" This is a decorator for a generator function which pauses the generator on yield\n", | ||
" until the given widget attribute changes. The new value of the attribute is\n", | ||
" sent to the generator and is the value of the yield.\n", | ||
" \"\"\"\n", | ||
" def f(iterator):\n", | ||
" @wraps(iterator)\n", | ||
" def inner():\n", | ||
" i = iterator()\n", | ||
" def next_i(change):\n", | ||
" try:\n", | ||
" i.send(change.new)\n", | ||
" except StopIteration as e:\n", | ||
" widget.unobserve(next_i, attribute)\n", | ||
" widget.observe(next_i, attribute)\n", | ||
" # start the generator\n", | ||
" next(i)\n", | ||
" return inner\n", | ||
" return f" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"Then we set up our generator." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"from ipywidgets import IntSlider, VBox, HTML\n", | ||
"slider2=IntSlider()\n", | ||
"\n", | ||
"@yield_for_change(slider2, 'value')\n", | ||
"def f():\n", | ||
" for i in range(10):\n", | ||
" print('did work %s'%i)\n", | ||
" x = yield\n", | ||
" print('generator function continued with value %s'%x)\n", | ||
"f()\n", | ||
"\n", | ||
"slider2" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"## Modifications\n", | ||
"\n", | ||
"The above two approaches both waited on widget change events, but can be modified to wait for other things, such as button event messages (as in a \"Continue\" button), etc." | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": { | ||
"nbsphinx": "hidden" | ||
}, | ||
"source": [ | ||
"[Index](Index.ipynb) - [Back](Widget Custom.ipynb)" | ||
] | ||
} | ||
], | ||
"metadata": { | ||
"kernelspec": { | ||
"display_name": "Python 3", | ||
"language": "python", | ||
"name": "python3" | ||
}, | ||
"language_info": { | ||
"codemirror_mode": { | ||
"name": "ipython", | ||
"version": 3 | ||
}, | ||
"file_extension": ".py", | ||
"mimetype": "text/x-python", | ||
"name": "python", | ||
"nbconvert_exporter": "python", | ||
"pygments_lexer": "ipython3", | ||
"version": "3.5.2" | ||
} | ||
}, | ||
"nbformat": 4, | ||
"nbformat_minor": 1 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters