交互函数#

ipywidgets.interact 自动创建用户界面(UI)控件,用于以交互方式探索代码和数据。

from ipywidgets import interact, interactive, fixed, interact_manual
import ipywidgets as widgets

基础的 interact#

在最基本的层面上,interact 自动为函数参数生成 UI 控件,然后在你交互式地操作这些控件时,使用这些参数调用函数。要使用 interact,你需要定义你想要探索的函数。

这里有函数,它返回其唯一的参数 x

def f(x):
    return x

当你将这个函数作为第一个参数传递给 interact,并提供一个整数关键字参数(x=10)时,会生成一个滑块并与函数参数绑定。

interact(f, x=10);

当你移动滑块时,函数会被调用,并打印其返回值。

如果你传递 TrueFalseinteract 将生成复选框:

interact(f, x=True);

如果你传递字符串,interact 将生成文本框。

interact(f, x='Hi there!');

interact 也可以作为装饰器使用。这允许你定义函数并立即与之交互。正如这个例子所展示的,interact 也适用于具有多个参数的函数。

@interact(x=True, y=1.0)
def g(x, y):
    return (x, y)

使用 fixed 固定参数#

有时你可能想要使用 interact 探索函数,但将一个或多个参数固定为特定值。这可以通过用 fixed 函数包装这些值来实现。

def h(p, q):
    return (p, q)

当调用 interact 时,传递 fixed(20)q 以将其固定在值 20

interact(h, p=5, q=fixed(20));

注意,只为 p 生成了滑块,因为 q 的值是固定的。

小部件缩写#

当你向 interact 传递整数值的关键字参数 10x=10)时,它会生成整数滑块控件,范围为 [-10,+3*10]。在这种情况下,10 是实际滑块小部件的缩写:

IntSlider(min=-10, max=30, step=1, value=10)

实际上,如果我们将这个 IntSlider 作为 x 的关键字参数传递,我们可以得到相同的结果:

interact(f, x=widgets.IntSlider(min=-10, max=30, step=1, value=10));

下表概述了不同的参数类型以及它们如何映射到交互控件:

关键字参数

小部件

TrueFalse

Checkbox

'Hi there'

Text

如果传递的是整数,则为value(min,max)(min,max,step)

IntSlider

如果传递的是浮点数,则为value(min,max)(min,max,step)

FloatSlider

['orange','apple'][('one', 1), ('two', 2)]

Dropdown

注意,如果给定一个列表或元组列表(表示离散选择),则使用下拉菜单;如果给定一个元组(表示范围),则使用滑块。

你已经看到了上面的复选框和文本小部件是如何工作的。在这里,我们提供了有关滑块和下拉菜单不同缩写的更多细节。

如果传递了一个整数的2元组 (min, max),则会生成具有这些最小值和最大值(包括边界)的整数滑块。在这种情况下,默认步长为 1

interact(f, x=(0,4));

如果传递了一个整数的 3 元组 (min,max,step),则还可以设置步长。

interact(f, x=(0,8,2));

如果元组中的 任何 元素是浮点数,则会产生浮点值滑块。这里最小值是 0.0,最大值是 10.0,步长是 0.1 (默认值)。

interact(f, x=(0.0,10.0));

通过在元组中传递第三个元素,可以更改步长。

interact(f, x=(0.0,10.0,0.01));

对于整数和浮点值滑块,你可以通过将默认关键字参数传递给底层的 Python 函数来设置小部件的初始值。在这里,我们将一个浮点滑块的初始值设置为 5.5

@interact(x=(0.0,20.0,0.5))
def h(x=5.5):
    return x

通过传递字符串列表来构造下拉菜单(Dropdown)。在这种情况下,这些字符串既用作下拉菜单 UI 中的名称,又传递给底层的 Python 函数。

interact(f, x=['apples','oranges']);

如果你想要向下传递非字符串值到 Python 函数的下拉菜单,你可以传递 ('label', value) 对的列表。第一个项是下拉菜单 UI 中的名称,第二个项是传递给底层 Python 函数的参数值。

interact(f, x=[('one', 10), ('two', 20)]);

最后,如果你需要比缩写提供的更精细的控制,你可以传递 ValueWidget 实例作为参数。ValueWidget 是旨在控制单个值的小部件。大多数与 ipywidgets 捆绑在一起的小部件都继承自 ValueWidget。有关更多信息,请参阅关于小部件类型的 WidgetCustom

interact(f, x=widgets.Combobox(options=["Chicago", "New York", "Washington"], value="Chicago"));

interactive#

除了 interact,IPython 还提供了另一个函数 interactive,当你想重用生成的小部件或访问绑定到 UI 控件的数据时,这个函数非常有用。

请注意,与 interact 不同,函数的返回值不会自动显示,但是你可以在函数内部使用 IPython.display.display 来显示值。

这是显示其两个参数之和并返回该和的函数。如果不希望显示函数的结果,可以省略 display 行。

from IPython.display import display
def f(a, b):
    display(a + b)
    return a+b

interact 不同,interactive 返回 Widget 实例,而不是立即显示小部件。

w = interactive(f, a=10, b=20)

这个小部件是 interactive,它是 VBox 的子类,VBox 是其他小部件的容器。

type(w)
ipywidgets.widgets.interaction.interactive

这个 interactive 的子对象是两个整数值滑块和一个输出小部件,由上述的小部件缩写生成。

w.children
(IntSlider(value=10, description='a', max=30, min=-10),
 IntSlider(value=20, description='b', max=60, min=-20),
 Output())

要实际显示这些小部件,你可以使用 IPython 的 display 函数。

display(w)

此时,UI 控件的工作方式就像使用了 interact 一样。你可以交互地操作它们,函数将被调用。然而,interactive 返回的小部件实例还允许你访问底层 Python 函数的当前关键字参数和返回值。

以下是当前的关键字参数。如果你在操作滑块后重新运行这个单元格,这些值将会发生变化。

w.kwargs
{'a': 10, 'b': 20}

这是函数当前的返回值。

w.result
30

禁用连续更新#

在与长时间运行的函数进行交互时,实时反馈不是有帮助的,反而是一种负担。请参阅以下示例:

def slow_function(i):
    print(int(i),list(x for x in range(int(i)) if 
                str(x)==str(x)[::-1] and 
                str(x**2)==str(x**2)[::-1]))
    return
%%time
slow_function(1e6)
1000000 [0, 1, 2, 3, 11, 22, 101, 111, 121, 202, 212, 1001, 1111, 2002, 10001, 10101, 10201, 11011, 11111, 11211, 20002, 20102, 100001, 101101, 110011, 111111, 200002]
CPU times: user 767 ms, sys: 0 ns, total: 767 ms
Wall time: 766 ms

请注意,即使在滑块上拖动鼠标时,输出也会更新。这对于长时间运行的函数来说并不有用,因为会导致卡顿:

from ipywidgets import FloatSlider
interact(slow_function,i=FloatSlider(min=1e5, max=1e7, step=1e5));

有两种方法可以缓解这个问题。你可以仅在需求时执行,或者将执行限制在鼠标释放事件上。

interact_manual#

interact_manual 函数提供了一种交互的变体,允许你限制执行,使其仅在需求时进行。在交互控件中添加了一个按钮,允许你触发执行事件。

interact_manual(slow_function,i=FloatSlider(min=1e5, max=1e7, step=1e5));

通过使用 dict 作为第二个参数,你也可以在 interactive 中实现同样的功能,如下所示。

slow = interactive(slow_function, {'manual': True}, i=widgets.FloatSlider(min=1e4, max=1e6, step=1e4))
# slow

continuous_update#

如果你正在使用滑块小部件,可以将 continuous_update 参数设置为 Falsecontinuous_update 是滑块小部件的一个参数,它将执行限制在鼠标释放事件上。

# interact(slow_function,i=FloatSlider(min=1e5, max=1e7, step=1e5, continuous_update=False));

对用户界面的更多控制:interactive_output#

interactive_output 提供了额外的灵活性:你可以控制 UI 元素如何布局。

interactinteractiveinteract_manual 不同,interactive_output 不会为小部件生成用户界面。这是强大的,因为这意味着你可以创建小部件,将其放入盒子中,然后将小部件传递给 interactive_output,并对小部件及其布局进行控制。

a = widgets.IntSlider()
b = widgets.IntSlider()
c = widgets.IntSlider()
ui = widgets.HBox([a, b, c])
def f(a, b, c):
    print((a, b, c))

out = widgets.interactive_output(f, {'a': a, 'b': b, 'c': c})

# display(ui, out)

相互依赖的参数#

使用 observe 可以手动表达相互依赖的参数。请参阅以下示例,其中一个变量用来描述另一个变量的边界。欲了解更多信息,请查看 小部件事件示例笔记本

x_widget = FloatSlider(min=0.0, max=10.0, step=0.05)
y_widget = FloatSlider(min=0.5, max=10.0, step=0.05, value=5.0)

def update_x_range(*args):
    x_widget.max = 2.0 * y_widget.value
y_widget.observe(update_x_range, 'value')

def printer(x, y):
    print(x, y)
# interact(printer,x=x_widget, y=y_widget);

闪烁和跳动的输出#

有时,你可能会注意到交互输出会闪烁和跳动,导致笔记本滚动位置在更新输出时发生变化。交互控件有一个布局,因此我们可以将其高度设置为一个合适的值(目前是手动选择的),以便在更新时不会改变大小。

%matplotlib inline
from ipywidgets import interactive
import matplotlib.pyplot as plt
import numpy as np

def f(m, b):
    plt.figure(2)
    x = np.linspace(-10, 10, num=1000)
    plt.plot(x, m * x + b)
    plt.ylim(-5, 5)
    plt.show()

interactive_plot = interactive(f, m=(-2.0, 2.0), b=(-3, 3, 0.5))
output = interactive_plot.children[-1]
output.layout.height = '350px'
# interactive_plot

与多个函数交互#

你可能想要让一个单独的小部件与多个函数进行交互。这可以通过简单地使用 interactive_output() 函数将小部件链接到两个函数来实现。这些函数的执行顺序将是它们被链接到小部件的顺序。

import ipywidgets as widgets
from IPython.display import display

a = widgets.IntSlider(value=5, min=0, max=10)

def f1(a):
    display(a)
    
def f2(a):
    display(a * 2)
    
out1 = widgets.interactive_output(f1, {'a': a})
out2 = widgets.interactive_output(f2, {'a': a})

display(a)
display(out1)
display(out2)