异步组件#
本笔记本涵盖两种场景,我们希望小部件相关代码在运行时不会阻止内核处理其他执行请求:
暂停代码以等待用户在前端与组件交互
在后台更新小部件
等待用户交互#
您可能希望暂停您的Python代码,等待前端小部件的某些用户交互。通常这很难实现,因为运行Python代码会阻止来自前端的任何小部件消息,直到Python代码执行完毕。
我们将通过两种方法实现:使用事件循环集成,以及使用纯生成器函数。
事件循环集成#
如果我们利用 IPython 提供的事件循环集成,我们可以使用 Python 3 中的 async/await 语法获得一个优雅的解决方案。
首先我们调用我们的异步事件循环。这需要ipykernel 4.7或更高版本。
%gui asyncio
我们定义一个新函数,当部件属性发生变化时返回一个future。
import asyncio
def wait_for_change(widget, value):
future = asyncio.Future()
def getvalue(change):
# make the new value available
future.set_result(change.new)
widget.unobserve(getvalue, value)
widget.observe(getvalue, value)
return future
最后我们来到等待部件变化的函数。我们将执行10个工作单元,并在每个单元之后暂停,直到观察到部件的改变。注意,部件的值对我们来说是可用的,因为它是wait_for_change future的结果。
运行此函数,并更改滑块10次。
from ipywidgets import IntSlider, Output
slider = IntSlider()
out = Output()
async def f():
for i in range(10):
out.append_stdout('did work ' + str(i) + '\n')
x = await wait_for_change(slider, 'value')
out.append_stdout('async function continued with value ' + str(x) + '\n')
asyncio.ensure_future(f())
slider
out
生成器方法#
如果您无法利用 async/await 语法,或者不想修改事件循环,也可以通过生成器函数实现此功能。
首先,我们定义一个装饰器,它将一个生成器函数挂接到小部件的变更事件上。
from functools import wraps
def yield_for_change(widget, attribute):
"""Pause a generator to wait for a widget change event.
This is a decorator for a generator function which pauses the generator on yield
until the given widget attribute changes. The new value of the attribute is
sent to the generator and is the value of the yield.
"""
def f(iterator):
@wraps(iterator)
def inner():
i = iterator()
def next_i(change):
try:
i.send(change.new)
except StopIteration as e:
widget.unobserve(next_i, attribute)
widget.observe(next_i, attribute)
# start the generator
next(i)
return inner
return f
然后我们设置我们的生成器。
from ipywidgets import IntSlider, VBox, HTML
slider2=IntSlider()
@yield_for_change(slider2, 'value')
def f():
for i in range(10):
print('did work %s'%i)
x = yield
print('generator function continued with value %s'%x)
f()
slider2
did work 0
修改#
上述两种方法都在等待小部件变更事件,但也可以修改为等待其他内容,例如按钮事件消息(如“继续”按钮)等。
在后台更新一个widget#
有时你希望在后台更新一个widget,使内核也能处理其他执行请求。我们可以通过线程来实现。在以下示例中,进度条将在后台更新,并允许主内核执行其他计算。
import threading
from IPython.display import display
import ipywidgets as widgets
import time
progress = widgets.FloatProgress(value=0.0, min=0.0, max=1.0)
def work(progress):
total = 100
for i in range(total):
time.sleep(0.2)
progress.value = float(i+1)/total
thread = threading.Thread(target=work, args=(progress,))
display(progress)
thread.start()