ipywidgets 简单事件#

Button 不是用来表示数据类型的。相反,按钮小部件用于处理鼠标点击。Buttonon_click 方法可以用来注册当按钮被点击时要调用的函数。on_click 的文档字符串可以在下面看到。

# 为了保证 `JupyterLite` 可用,需要 notebook 开头添加:
%pip install -q ipywidgets
Note: you may need to restart the kernel to use updated packages.
import ipywidgets as widgets
print(widgets.Button.on_click.__doc__)
Register a callback to execute when the button is clicked.

        The callback will be called with one argument, the clicked button
        widget instance.

        Parameters
        ----------
        remove: bool (optional)
            Set to true to remove the callback from the list of callbacks.
        

示例#

由于按钮点击是无状态的,它们通过自定义消息从前端传输到后端。通过使用 on_click 方法,下面展示了在点击时打印消息的按钮。要捕获 print (或其他任何类型的输出)并确保其显示,请确保将其发送到 Output 小部件(或将您想要显示的信息放入 HTML 小部件)。

from IPython.display import display
import ipywidgets as widgets
button = widgets.Button(description="点击")
output = widgets.Output(layout={'border': '1px solid black'})
def on_button_clicked(b):
    with output:
        print("Button clicked.")

button.on_click(on_button_clicked, remove=False)
display(button, output)

Traitlet 事件#

小部件属性是 IPython traitlets,而 traitlets 是有事件的。要处理变化,可以使用小部件的 observe 方法注册回调函数。observe 的文档字符串可以在下面看到。

print(widgets.Widget.observe.__doc__)
Setup a handler to be called when a trait changes.

        This is used to setup dynamic notifications of trait changes.

        Parameters
        ----------
        handler : callable
            A callable that is called when a trait changes. Its
            signature should be ``handler(change)``, where ``change`` is a
            dictionary. The change dictionary at least holds a 'type' key.
            * ``type``: the type of notification.
            Other keys may be passed depending on the value of 'type'. In the
            case where type is 'change', we also have the following keys:
            * ``owner`` : the HasTraits instance
            * ``old`` : the old value of the modified trait attribute
            * ``new`` : the new value of the modified trait attribute
            * ``name`` : the name of the modified trait attribute.
        names : list, str, All
            If names is All, the handler will apply to all traits.  If a list
            of str, handler will apply to all names in the list.  If a
            str, the handler will apply just to that name.
        type : str, All (default: 'change')
            The type of notification to filter by. If equal to All, then all
            notifications are passed to the observe handler.
        

签名#

在文档字符串中提到,注册的回调函数必须具有 handler(change) 的形式,其中 change 是一个字典,用于保存有关更改的信息。

通过使用该方法,下面是一个示例,展示了如何在 IntSlider 的值发生变化时输出其值。

int_range = widgets.IntSlider()
output2 = widgets.Output(layout={'border': '1px solid black'})

display(int_range, output2)

def on_value_change(change):
    with output2:
        print(change['new'])

int_range.observe(on_value_change, names='value')

链接小部件#

通常,您可能希望简单地将小部件属性链接在一起。与使用裸 traitlets 事件相比,属性同步可以通过更简单的方式进行。

在内核中链接 traitlets 属性#

第一种方法是使用 traitlets 模块中的 linkdlink 函数(这两个函数由 ipywidgets 模块重新导出以方便使用)。这只适用于我们正在与实时内核进行交互的情况。

caption = widgets.Label(value='The values of slider1 and slider2 are synchronized')
sliders1, slider2 = widgets.IntSlider(description='Slider 1'),\
                    widgets.IntSlider(description='Slider 2')
l = widgets.link((sliders1, 'value'), (slider2, 'value'))
display(caption, sliders1, slider2)
caption = widgets.Label(value='Changes in source values are reflected in target1')
source, target1 = widgets.IntSlider(description='Source'),\
                  widgets.IntSlider(description='Target 1')
dl = widgets.dlink((source, 'value'), (target1, 'value'))
display(caption, source, target1)

traitlets.linktraitlets.dlink 函数返回 LinkDLink 对象。通过调用 unlink 方法可以断开链接。

# l.unlink()
# dl.unlink()

在内核中注册回调以响应 trait 的变化#

由于 Python 端的部件属性是 traitlets,因此每当模型从前端获取更新时,您可以注册处理程序来处理更改事件。

传递给 observe 的处理程序将使用 change 参数进行调用。change 对象至少包含 type 键和 name 键,分别对应于通知的类型和触发通知的属性的名称。

根据 type 的值,可能会传递其他键。在 typechange 的情况下,我们还有以下几个键:

  • owner : HasTraits 实例

  • old : 修改后的 trait 属性的旧值

  • new : 修改后的 trait 属性的新值

  • name : 修改后的 trait 属性的名称。

caption = widgets.Label(value='The slider value is in its initial position.')
slider = widgets.IntSlider(min=-5, max=5, value=1, description='Slider')

def handle_slider_change(change):
    caption.value = 'The slider value is ' + (
        'negative' if change.new < 0 else 'nonnegative'
    )

slider.observe(handle_slider_change, names='value')

display(caption, slider)

从客户端链接小部件属性#

在同步 traitlets 属性时,由于往返服务器端的延迟,您可能会遇到延迟。您还可以使用链接小部件直接在浏览器中链接小部件属性,可以选择单向或双向方式。

在没有内核的情况下将小部件嵌入到 html 网页中时,Javascript 链接会持续存在。

caption = widgets.Label(value='The values of range1 and range2 are synchronized')
range1, range2 = widgets.IntSlider(description='Range 1'),\
                 widgets.IntSlider(description='Range 2')
l = widgets.jslink((range1, 'value'), (range2, 'value'))
display(caption, range1, range2)
caption = widgets.Label(value='Changes in source_range values are reflected in target_range1')
source_range, target_range1 = widgets.IntSlider(description='Source range'),\
                              widgets.IntSlider(description='Target range 1')
dl = widgets.jsdlink((source_range, 'value'), (target_range1, 'value'))
display(caption, source_range, target_range1)

widgets.jslink 函数返回 Link 小部件。通过调用 unlink 方法可以断开链接。

# l.unlink()
# dl.unlink()

内核链接和客户端链接的区别#

内核链接是指通过 Python 进行链接。如果在内核中链接了两个滑块,当一个滑块发生变化时,浏览器会向内核(在这种情况下是 Python)发送消息,更新变化的滑块,然后内核中的链接小部件将更改传播到内核中的另一个滑块对象,然后另一个滑块的内核对象会向浏览器发送消息,以更新浏览器中另一个滑块的视图。如果内核没有运行(如在静态网页中),那么控件将不会被链接。

使用 jslink (即,在浏览器端)进行链接意味着在 JavaScript 中构建链接。当一个滑块发生变化时,浏览器中运行的 JavaScript 会更改浏览器中另一个滑块的值,完全不需要与内核进行通信。如果滑块附加到内核对象,每个滑块将独立地更新它们在内核端的对象。

要了解两者之间的区别,请访问 ipywidgets 文档中此页面的静态版本,并尝试使用页面底部附近的滑块。使用 linkdlink 在内核中链接的那些滑块不再链接,但是使用 jslinkjsdlink 在浏览器中链接的那些滑块仍然保持链接。

持续更新#

一些部件提供了在其 continuous_update 属性中进行选择的选项,可以在持续更新值和仅在用户提交值时(例如,通过按下 Enter 键或从控件导航离开)才更新值之间进行选择。在接下来的示例中,我们看到“延迟”控件仅在用户完成拖动滑块或提交文本框后传输其值。而“连续”控件则在其值发生变化时持续传输它们的值。尝试在每个文本框中输入一个两位数,或拖动每个滑块,以观察差异。

a = widgets.IntSlider(description="Delayed", continuous_update=False)
b = widgets.IntText(description="Delayed", continuous_update=False)
c = widgets.IntSlider(description="Continuous", continuous_update=True)
d = widgets.IntText(description="Continuous", continuous_update=True)

widgets.jslink((a, 'value'), (b, 'value'))
widgets.jslink((a, 'value'), (c, 'value'))
widgets.jslink((a, 'value'), (d, 'value'))
widgets.VBox([a,b,c,d])

滑块、TextTextarea 控件默认设置为 continuous_update=TrueIntText 和其他用于输入整数或浮点数的文本框默认设置为 continuous_update=False(因为通常你会想在按下 Enter 键或导航离开文本框之前输入完整的数字)。