PanelでUIの作成#
Panelは、Webベースのダッシュボードインターフェースを開発するための拡張ライブラリです。Bokehが提供するウィジェットとレイアウト要素を基盤としており、Jupyter NotebookやBokehサーバー内でWebユーザーインターフェースとPythonプログラム間のリアルタイムデータ通信を実現します。Panelを使用すると、Webアプリケーションやデータ可視化のダッシュボードインターフェースを迅速に開発できます。
まず、HoloViewsとPanelライブラリを読み込み、pn.extension()
を呼び出してPanelのJupyter Notebookプラグインを初期化します。そのパラメータは読み込むプラグイン名で、ここではkatexプラグインとaceプラグインを読み込みます。katexプラグインはLaTeX数式を表示するために使用されます。
import panel as pn
import numpy as np
import holoviews as hv
hv.extension("bokeh")
pn.extension("katex")
UIの作成方法#
次の plot_sin_wave()
は、4 つの引数を受け取り、減衰する正弦波の波形データを計算し、holoviews
を用いて曲線グラフを返します。本節では、この関数を利用して 4 つの引数を設定する UI を作成し、ウィジェットの値が変更されるとグラフも更新される UI を実装します。
def plot_sin_wave(freq, amp, phase, dump):
t = np.linspace(0, 10, 400)
y = amp * np.exp(-dump * t) * np.sin(2 * np.pi * freq * t + np.deg2rad(phase))
return hv.Curve((t, y)).opts(width=600, show_grid=True)
interact
#
次のコードは、interact
を使用して plot_sin_wave
関数の引数をインタラクティブに調整できる UI を作成しています。 interact
は、関数の引数をインタラクティブに変更できるウィジェットを自動生成します。interact
のキーワード引数で、plot_sin_wave
関数の各引数の値の範囲を指定します。
引数の値の指定方法によって、生成されるウィジェットの種類が異なります:
タプル
(min, max)
を指定すると、連続値を調整できるスライダーが作成されます。range
やリストを指定すると、離散的な値を選択できるスライダーが作成されます。
ui = pn.interact(
plot_sin_wave, freq=(1, 10), amp=(1, 10), phase=range(0, 360, 45), dump=(0.0, 1.0)
)
ui
次のコードで作成された UI オブジェクトの構造を表示します。
外側の
Column
は、全体のレイアウトをまとめるコンテナです。内部の
Column
は、各ウィジェットを配置するためのレイアウトです。内部の
Row
には、グラフを表すHoloViews
オブジェクトが含まれています。
print(ui)
Column(sizing_mode='fixed')
[0] Column
[0] IntSlider(end=10, name='freq', start=1, value=5)
[1] IntSlider(end=10, name='amp', start=1, value=5)
[2] DiscreteSlider(formatter='%d', name='phase', options=[0, 45, 90, 135, ...], value=0)
[3] FloatSlider(name='dump', value=0.5)
[1] Row(sizing_mode='fixed')
[0] HoloViews(Curve, height=300, name='interactive01348', sizing_mode='fixed', width=600)
Column
や Row
などのレイアウトは、objects
属性に入れ子構造の要素を保持します。 次のコードでは、外側のレイアウトを Row
に変更し、ウィジェットとグラフを左右に並べます。元の UI のウィジェットとグラフのオブジェクトをそのまま使用するため、新しい UI で値を変更すると、元のグラフも同時に更新されます。
pn.Row(*ui.objects)
コールバック型#
interact()
は便利ですが、カスタマイズ性が低いため、複雑な UI を作成する場合には適していません。自由に UI を作成するには、ウィジェットやグラフなどのコンポーネントを個別に作成し、それらを適切に連携させる必要があります。連携方法はいくつかありますが、次のコードでは、UI 開発で最もよく使われるコールバックを利用し、ウィジェットの値の変化に応じてグラフを更新する方法を示します。
❶ ウィジェットの value
属性に対する変更を監視し、その変更が発生するたびに update_figure
関数を実行するように、コールバック関数を登録します。
❷ コールバック関数内で描画関数 plot_sin_wave()
を呼び出し、その結果を fig_panel.object
に代入して、グラフを更新します。
❸ UI を表示する前に、一度 update_figure()
を呼び出して初期のグラフを生成します。
freq = pn.widgets.FloatSlider(value=5, start=1, end=10, name="freq")
amp = pn.widgets.FloatSlider(value=1, start=1, end=5, name="amp")
phase = pn.widgets.Select(
value=90, options=np.arange(0, 360, 45).tolist(), name="phase"
)
dump = pn.widgets.Spinner(value=0.5, step=0.1, name="dump")
ctrl_panel = pn.Column(freq, amp, phase, dump)
fig_panel = pn.pane.HoloViews()
def update_figure(*events):
fig_panel.object = plot_sin_wave(
freq.value, amp.value, phase.value, dump.value
) # ❷
for widget in [freq, amp, phase, dump]:
widget.param.watch(update_figure, "value") # ❶
update_figure() # ❸
pn.Row(ctrl_panel, fig_panel)
bind
型#
コールバックベースのコードは複雑で、メンテナンスしにくいことがあります。Panel では、bind()
関数を提供して、複数のウィジェットとそのコールバック関数を結びつけることができます。これにより、コードが簡潔になり、可読性とメンテナンス性が向上します。
❶ bind()
は plot_sin_wave
関数をウィジェットの値と結びつけるために使用します。このコードは、plot_sin_wave
関数をウィジェット freq
, amp
, phase
, dump
と連携させ、ウィジェットの値が変更されるたびに自動的に関数を再実行させます。
❷ bind()
のリターン値は特殊な関数 binded_func
で、これを直接 HoloViews
パネルに渡すことができます。この関数が実行されると、ウィジェットの値が変更されるたびに自動的にグラフを更新します。
この方法により、ウィジェットとグラフの更新が簡単に結びつけられ、コードがシンプルになります。
freq = pn.widgets.FloatSlider(value=5, start=1, end=10, name="freq")
amp = pn.widgets.FloatSlider(value=1, start=1, end=5, name="amp")
phase = pn.widgets.Select(
value=90, options=np.arange(0, 360, 45).tolist(), name="phase"
)
dump = pn.widgets.Spinner(value=0.5, step=0.1, name="dump")
ctrl_panel = pn.Column(freq, amp, phase, dump)
binded_func = pn.bind(plot_sin_wave, freq, amp, phase, dump) # ❶
fig_panel = pn.pane.HoloViews(binded_func) # ❷
pn.Row(ctrl_panel, fig_panel)
Parameterized
型#
前の例では、ウィジェットを手動で作成する必要がありました。しかし、param.Parameterized
クラスを継承すれば、すべてのパラメータをひとつのクラスにまとめ、パラメータの定義から自動的にウィジェットの UI を生成できます。Param ライブラリについては、本章の最後の節で詳しく説明します。
次のコードでは、param.Parameterized
を継承した WaveParam
クラスを作成し、param.Number
や param.Selector
などを用いて属性を定義します。
❶WaveParam
のオブジェクトを作成します。 ❷このオブジェクトを Row() に直接渡すと、UI ウィジェットが自動的に作成されます。
❸bind()
に pars.param['freq']
などのパラメータを渡すと、前の例と同様に、パラメータの値が変更されたときに plot_sin_wave()
が実行されます。
import param
class WaveParam(param.Parameterized):
freq = param.Number(default=5, bounds=(1, 10))
amp = param.Number(default=1, bounds=(1, 5))
phase = param.Selector(default=90, objects=np.arange(0, 360, 45).tolist())
dump = param.Number(default=0.5, step=0.1)
pars = WaveParam() # ❶
layout = pn.Row(
pars, # ❷
pn.bind(
plot_sin_wave, *[pars.param[name] for name in ["freq", "amp", "phase", "dump"]]
), # ❸
)
layout
作成された UI の構造は以下のようになります。
print(layout)
Row
[0] Column(margin=(5, 10), name='WaveParam')
[0] StaticText(value='<b>WaveParam</b>')
[1] FloatSlider(end=10, name='Freq', start=1, value=5)
[2] FloatSlider(end=5, name='Amp', start=1, value=1)
[3] Select(name='Phase', options=OrderedDict({'0': 0, ...]), value=90)
[4] FloatInput(name='Dump', value=0.5)
[1] ParamFunction(function, _pane=HoloViews, defer_load=False)
次のように、グラフを更新する関数を WaveParam
クラスのメソッドとして定義することもできます。
❶ bind()
の代わりに、depends()
を使って WaveParam
クラスのメソッドをデコレーションすると、このメソッドと属性を連携できます。
❷ pars
と pars.plot
を Row
に渡せば、UI とグラフを同時に作成できます。
class WaveParam(param.Parameterized):
freq = param.Number(default=5, bounds=(1, 10))
amp = param.Number(default=1, bounds=(1, 5))
phase = param.Selector(default=90, objects=np.arange(0, 360, 45).tolist())
dump = param.Number(default=0.5, step=0.1)
@pn.depends("freq", "amp", "phase", "dump") # ❶
def plot(self):
return plot_sin_wave(self.freq, self.amp, self.phase, self.dump)
pars = WaveParam()
layout = pn.Row(pars, pars.plot) # ❷
layout
フーリエ級数のUI#
この例では、ユーザーはUIを使用して、さまざまな数学関数のフーリエ級数の波形を表示できます。”function”という名前のドロップダウン選択ボックスで関数を選択し、”order”という名前の整数スライダーでフーリエ級数の最高次数を設定します。展開されたフーリエ級数は下部に表示され、フーリエ級数の曲線は右側のチャートに表示されます。
以下は計算部分のプログラムです。calc_fourier(func, order)
は数学関数func
をフーリエ級数展開し、その中の最初のorder
項を取得します。2つの戻り値があります:フーリエ級数のLaTeXテキストと最初のorder
項の曲線(Curve
)オブジェクトです。プログラムの計算速度を向上させるために、functools.lru_cache()
を使用して計算結果をキャッシュします。
from functools import lru_cache
import numpy as np
from sympy import Piecewise, S, Symbol, And, fourier_series, lambdify, latex, pi
@lru_cache()
def calc_fourier(func, order):
x = Symbol("x")
fs = fourier_series(func, (x, 0, 2))
fs_t = fs.truncate(n=order)
fs_lambda = lambdify([x], fs_t)
x_ = np.linspace(0, 4, 200)
y_ = fs_lambda(x_)
return f"$${latex(fs_t)}$$", hv.Curve((x_, y_))
x = Symbol("x")
functions = {
"step": Piecewise((1, x < 1), (0, True)),
"triangle": Piecewise((x, x < 1), (2 - x, True)),
"pulse": Piecewise((1, And(S(2) / 3 < x, x < S(4) / 3)), (0, True)),
"trapezoid": Piecewise(
(S(3) / 2 * x, x < S(2) / 3), (1, x < S(4) / 3), (3 - S(3) / 2 * x, True)
),
"sawtooth": x,
}
以下に、ステップ関数のフーリエ級数の最初の5項の式とそのグラフを表示します。
from IPython.display import display_latex
formula, curve = calc_fourier(functions["step"], 5)
display_latex(formula, raw=True)
curve.opts(width=600, height=300)
リアクティブ型#
以下に、calc_fourier()
のUIを作成します。ここでは、すべてのインターフェース要素をFourierSeriesViewer
クラスにラップします。
❶ 2つのウィジェットを作成します。ドロップダウン選択ボックスウィジェットと整数スライダーウィジェットで、それぞれ数学関数名と最高次数を選択できるようにします。
❷ bind()
でcalc_fourier
関数と2つのウィジェットを連携させます。
❸ calc_fourier
関数は2つの値を返すため、そのままPane
オブジェクトに渡して表示することができません。ここでrx()
メソッドで得られたオブジェクトに[]
でインデックス操作を加えて、得られた値をLaTeX
とHoloViews
に渡します。
❹ 最後に、UIを構成するレイアウトオブジェクトを作成します。
class FourierSeriesViewer:
def __init__(self, functions):
self.functions = functions
self.func_name = pn.widgets.Select(name="function", options=self.functions) # ❶
self.order = pn.widgets.IntSlider(
name="order", value=5, start=1, end=10, step=1
)
func = pn.bind(calc_fourier, self.func_name, self.order) # ❷
self.latex_pane = pn.pane.LaTeX(func.rx()[0]) # ❸
self.curve_pane = pn.pane.HoloViews(func.rx()[1].opts(width=600, height=300))
self.layout = pn.Column( # ❹
pn.Row(pn.Column(self.func_name, self.order), self.curve_pane),
self.latex_pane,
)
fsv = FourierSeriesViewer(functions)
fsv.layout
rx()
は、関数や式を「リアクティブ」にするために使用します。リアクティブにすることで、入力が変わった場合に自動的に出力を再計算します。bind()
は引数と関数の出力をリアクティブにしますが、rx()
はすべてのPythonの演算をリアクティブにすることができます。
例えば、次のfunc()
は2つの値を返しますが、pn.bind(func, x=slider).rx()[1]
で、この関数の2つ目の返り値とslider
を連携させることができます。
slider = pn.widgets.FloatSlider(name="x", value=5, start=1, end=10)
def func(x):
return [x + 1, x * 10]
pn.bind(func, x=slider).rx()[1]
コールバック型#
param.Parameterized
の派生クラスのParameter
属性を使用すると、ウィジェットの作成とイベント処理を簡素化できます。以下のプログラムでは、param.Parameterized
の派生クラスFourierSeriesViewer2
を定義し、2つのParameter
属性func
とorder
を定義します。
初期化メソッド__init__()
では、
❶ self.param.func.objects
を使用してfunc
属性の候補値を設定します。
❷ UIが表示される前に、latex_pane
とcurve_pane
の内容を設定する必要があるため、functions
の最初の値をfunc
属性に設定すると、calc()
メソッドが実行されます。
❸ param.depends
デコレータを使用してコールバックメソッドcalc()
を設定します。calc()
メソッドを直接UIに渡さない場合は、watch=True
を指定する必要があります。こうすることで、func
やorder
が変化するとcalc()
が自動的に実行されます。このメソッド内でcalc_fourier()
の戻り値をlatex_pane.object
とcurve_pane.object
に設定することで、UIの数式とグラフが更新されます。
import param
class FourierSeriesViewer2(param.Parameterized):
func = param.Selector()
order = param.Integer(default=5, bounds=(1, 10))
def __init__(self, functions, **kw):
super().__init__(**kw)
self.param.func.objects = functions # ❶
self.latex_pane = pn.pane.LaTeX()
self.curve_pane = pn.pane.HoloViews()
self.layout = pn.Column(pn.Row(self, self.curve_pane), self.latex_pane)
self.func = next(iter(functions.values())) # ❷
@param.depends("func", "order", watch=True) # ❸
def calc(self):
order = self.order
formula, curve = calc_fourier(self.func, order)
self.latex_pane.object = formula
self.curve_pane.object = curve.opts(width=500, show_grid=True)
fsv2 = FourierSeriesViewer2(functions)
fsv2.layout.servable()
Panelのクラス類#
Panel のユーザーインターフェースは、次の 3 つの要素で構成されます。
Widget: 数値入力ボックス、ボタン、ドロップダウンリストなどの各種ウィジェット要素。
Pane: インターフェース内に表示できるオブジェクトをラップする要素。例えば、Bokeh や HoloViews のチャート、Markdown テキストなどを表示できます。
Panel: 各種要素のレイアウトを管理する要素。例えば、ウィジェットや Pane を横並び・縦並び・グリッド形式で配置できます。
すべてのWidget
類はpanel.widgets
モジュール内で定義されており、panel.widgets.base.Widget
を継承しています。以下に継承関係を示します。
from helper.python import print_subclasses
text = print_subclasses(pn.widgets.base.Widget, return_str=True)
pn.widgets.TextAreaInput(
value=text, width=500, height=300, styles={"font-family": "monospace"}
)
全てのPane
類はpanel.pane
モジュール内で定義されており、panel.pane.base.PaneBase
を継承しています。以下に継承関係を示します。
text = print_subclasses(pn.pane.base.PaneBase, return_str=True)
pn.widgets.TextAreaInput(
value=text, width=500, height=300, styles={"font-family": "monospace"}
)
以下に、一般的な Pane クラスをいくつか挙げます。
Markdown: Markdown テキストを HTML に変換して表示
HTML: 文字列を HTML として表示
DataFrame: Pandas の DataFrame を表として表示
LaTeX: LaTeX の数式を表示
Video: 動画を再生
Audio: 音声を再生
Ace: コードエディタを表示
HoloViews: HoloViews のチャートを表示
Bokeh: Bokeh のチャートを表示
ParamFunction / ParamMethod: 関数やメソッドの戻り値に基づいて適切な Pane オブジェクトを選択して表示
全てのPanel
類はpanel.layout
モジュール内で定義されており、panel.layout.base.Panel
を継承しています。以下に継承関係を示します。
text = print_subclasses(pn.layout.base.Panel, return_str=True)
pn.widgets.TextAreaInput(
value=text, width=500, height=300, styles={"font-family": "monospace"}
)
Paramてパラメータを定義#
Panelのすべてのクラスはparam.Parameterized
から継承され、すべての属性はParameter属性です。この章の最初のセクションとして、まずParamライブラリの使用方法を簡単に紹介し、以降の章の基礎を築きます。
Python言語の動的特性により、class
を使用してクラスを定義する場合、通常__init__()
メソッドでインスタンスの各種属性を初期化します。この属性は属性値を保存することしかできません。一方、Paramライブラリを使用して属性を定義する場合、属性値を設定するだけでなく、属性タイプ、値の範囲、説明ドキュメントなどの追加情報を設定することもできます。
Parameter属性#
すべてのParameter属性を持つクラスはparam.Parameterized
クラスから継承されます。Parameter属性はclass
の下でparam.Parameter
の派生クラスを使用して定義されます。以下の例では、❶Calculator
クラスはparam.Parameterized
クラスから継承され、4つのParameter属性を持っています:op1
、op2
、operator
、result
。❷op1
とop2
の属性タイプはNumber
で、パラメータdefault
、step
、bounds
、label
を使用して、それぞれ属性のデフォルト値、ステップサイズ、値の範囲、ラベルテキストを指定しています。❸operator
の属性タイプはSelector
で、この属性の値は候補値リストobjects
のいずれかの要素です。❹result
の属性タイプはString
です。Number
、Selector
、String
はすべてparam.Parameter
の派生クラスで、param.Parameterized
の派生クラスでは、これらのParameterオブジェクトを使用してParameter属性を定義します。
import param
class Calculator(param.Parameterized): # ❶
op1 = param.Number(default=2.0, step=0.1, bounds=(-10, 10), label="演算数1") # ❷
op2 = param.Number(default=3.0, step=0.1, bounds=(-10, 10), label="演算数2")
operator = param.Selector(objects=list("+-*/"), label="演算子") # ❸
result = param.String(label="結果") # ❹
Parameter属性を定義するために使用されるすべてのクラスはparam.Parameter
から継承されます。次のテーブルには、一般的に使用されるParameter派生クラスの一部がリストされています:
Parameterタイプ |
説明 |
主なキーワードパラメータ |
---|---|---|
|
文字列 |
|
|
数値 |
|
|
ブール値 |
|
|
ファイルパス |
|
|
リスト |
|
|
タプル |
|
|
pandasの |
|
|
リスト選択 |
|
次のコードは、すべてのParameter派生クラスの継承関係が表示されています。
print_subclasses(param.parameterized.Parameter)
└──Parameter
├──String
├──Dynamic
│ └──Number
│ ├──Integer
│ ├──Magnitude
│ ├──Date
│ └──CalendarDate
├──Boolean
│ └──Event
├──Tuple
│ └──NumericTuple
│ ├──XYCoordinates
│ └──Range
│ ├──DateRange
│ └──CalendarDateRange
├──Callable
│ └──Action
├──Composite
├──SelectorBase
│ ├──Selector
│ │ ├──ObjectSelector
│ │ ├──FileSelector
│ │ └──ListSelector
│ │ └──MultiFileSelector
│ └──ClassSelector
│ ├──Dict
│ │ └──ChildDict
│ ├──Array
│ ├──DataFrame
│ ├──Series
│ └──Child
├──_SignatureSelector
│ └──Selector
│ ├──ObjectSelector
│ ├──FileSelector
│ └──ListSelector
│ └──MultiFileSelector
├──List
│ ├──HookList
│ ├──Children
│ └──_ListValidateWithCallable
├──Path
│ ├──Filename
│ └──Foldername
├──Color
├──Bytes
├──Align
├──Aspect
├──Margin
└──CallbackException
以下にCalculator
オブジェクトを作成し、その内容を確認します。すべてのParameter属性値が自動的に表示され、属性name
も自動的に追加されていることがわかります。すべてのParameter属性値はデフォルト値です。
c1 = Calculator()
c1
Calculator(name='Calculator01748', op1=2.0, op2=3.0, operator='+', result='')
Parameterized
オブジェクトを作成する際、キーワードパラメータを使用してParameter属性の値を設定できます。例えば:
Calculator(op1=4, op2=5, operator="*")
Calculator(name='Calculator01749', op1=4, op2=5, operator='*', result='')
Parameter属性値の取得と設定は通常の属性と同じです:
c1.op1 = 10
c1
Calculator(name='Calculator01748', op1=10, op2=3.0, operator='+', result='')
Parameter属性にはタイプと値の範囲の検証機能があり、検証に失敗するとValueError
例外がスローされます。例外メッセージには例外が発生した理由が表示されます。
try:
c1.op1 = 100
except ValueError as ex:
print(ex)
try:
c1.result = 1.5
except ValueError as ex:
print(ex)
Number parameter 'Calculator.op1' must be at most 10, not 100.
String parameter 'Calculator.result' only takes a string value, not value of <class 'float'>.
Parameter属性のその他の情報と関連メソッドは、param
属性を通じてアクセスできます。以下のプログラムは、属性op1
の値の範囲を取得します。c1.param.op1
はparam.Parameter
派生クラスNumber
のオブジェクトです。
print(f"{c1.param.op1 = }")
print(f"{c1.param.op1.bounds = }")
c1.param.op1 = <param.parameters.Number object at 0x000002E5A2A03580>
c1.param.op1.bounds = (-10, 10)
set_param()
メソッドを呼び出すことで、複数の属性値を一度に設定できます:
c1.param.update(op1=10, op2=-3, operator="*")
c1
Calculator(name='Calculator01748', op1=10, op2=-3, operator='*', result='')
Parameterized
クラスは、そのインスタンスオブジェクトとしても使用できます。例えば:
print(f"{Calculator.op1 = }")
try:
Calculator.op1 = 100
except ValueError as ex:
print(ex)
Calculator.op1 = 2.0
Number parameter 'Calculator.op1' must be at most 10, not 100.
クラスの`Parameter属性値を変更すると、その属性のデフォルト値も同時に変更されます:
Calculator.op1 = 6
print(f"{Calculator.param.op1.default = }")
Calculator()
Calculator.param.op1.default = 6
Calculator(name='Calculator01750', op1=6, op2=3.0, operator='+', result='')
Parameterオブジェクトの属性per_instance
がデフォルト値True
の場合、各Parameterized
インスタンスのParameterオブジェクトは独立しています。以下のプログラムは、Calculator
オブジェクトc2
を作成し、その属性op1
の値の範囲を(-100, 100)に変更します。これにより、c2.op1
に100を代入しても例外がスローされなくなります。
c2 = Calculator()
c2.param.op1.bounds = -100, 100
c2.op1 = 100
print(f"{c1.param.op1.bounds = }, {c2.param.op1.bounds =}")
c1.param.op1.bounds = (-10, 10), c2.param.op1.bounds =(-100, 100)
コールバック関数#
Parameter属性に値を代入する際、Parameterized
の派生クラスのオブジェクトは指定されたコールバック関数を呼び出すことができます。以下の例では、❶c1.param.watch()
を使用してc1
のParameter属性にコールバック関数を追加しています。最初の引数はコールバック関数で、2番目の引数は属性名の文字列または属性名のリストです。onlychanged
パラメータがFalse
の場合、属性に値を代入するたびにコールバック関数がトリガーされます。True
の場合、属性値が変更されたときのみコールバック関数がトリガーされます。watch()
の戻り値はWatcher
オブジェクトです。
コールバック関数を呼び出す際、渡される引数の数は不定であるため、❷コールバック関数callback()
は可変引数*events
を使用してすべての引数値を受け取ります。各引数はEvent
オブジェクトで、コールバック関数をトリガーしたParameter属性に対応します。❸単一の属性に値を代入する場合、コールバック関数callback()
の引数は1つのEvent
オブジェクトです。❹set_param()
メソッドを使用して2つの属性に値を代入する場合、コールバック関数の引数は2つのEvent
オブジェクトになります。
以下にEvent
オブジェクトの各属性を示します:
what
:Parameter属性のどの情報がコールバック関数をトリガーしたか、’value’は属性値がトリガーしたことを示します。name
:Parameter属性名obj
:コールバック関数をトリガーしたオブジェクトcls
:コールバック関数をトリガーしたオブジェクトの型old
:代入前の値new
:代入後の値type
:’set’は代入を示し、’change’は属性値が変更されたことを示します
Parameter属性に値を代入するたびにコールバック関数がトリガーされるため、コールバック関数が時間のかかる場合、プログラムの実行性能に影響を与える可能性があります。❺コンテキストマネージャparam.batch_watch()
を使用して、属性の代入をバッチ処理することができます。with
文ブロック内では、すべての属性代入がコールバック関数をトリガーしません。with
文が終了する際、最後の代入結果を使用してコールバック関数がトリガーされます。❻c1.param.unwatch()
を使用して指定されたコールバック関数を登録解除することができます。引数はc1.param.watch()
が返すWatcher
オブジェクトです。
def callback(*events): # ❷
for event in events:
print(
f"obj: {event.obj.name if event.obj is not None else None}, ",
f"what: {event.what}, name: {event.name}, "
f"old: {event.old}, new: {event.new}, type: {event.type}",
)
print()
c1 = Calculator(op1=0, op2=0, name="c1")
watch = c1.param.watch(callback, ["op1", "op2"], onlychanged=False) # ❶
print("c1.op1 = 5")
c1.op1 = 5 # ❸
print("c1.update(op1=3, op2=6)")
c1.param.update(op1=3, op2=6) # ❹
print("batch_watch() for loop")
with param.parameterized.batch_call_watchers(c1): # ❺
for i in range(8):
c1.op1 = i
c1.op2 = i + 1
c1.param.unwatch(watch) # ❻
print("c1.op1 = 6")
c1.op1 = 6
c1.op1 = 5
obj: c1, what: value, name: op1, old: 0, new: 5, type: set
c1.update(op1=3, op2=6)
obj: c1, what: value, name: op1, old: 5, new: 3, type: set
obj: c1, what: value, name: op2, old: 0, new: 6, type: set
batch_watch() for loop
obj: c1, what: value, name: op1, old: 6, new: 7, type: set
obj: c1, what: value, name: op2, old: 7, new: 8, type: set
c1.op1 = 6
watch()
メソッドのwhat
パラメータを使用して、Parameter属性の他の情報のコールバック関数を指定できます。以下のプログラムは、属性op2
の値の範囲にコールバック関数を追加します。値の範囲が変更されると、指定されたコールバック関数が呼び出されます。
c1.param.watch(callback, "op2", what="bounds")
c1.param.op2.bounds = (0, 1000)
obj: None, what: bounds, name: op2, old: (-10, 10), new: (0, 1000), type: changed
Parameterized
派生クラスを定義する際、デコレータparam.depends()
を使用してメソッドをコールバックメソッドとして設定することもできます。以下の例では、❶Calculator2
はCalculator
から継承し、初期化メソッド__init__()
で可変引数**kw
を使用してすべてのパラメータを受け取ります。❷Parameter属性が正しく割り当てられるように、他の初期化作業を行う前に、まず基底クラスの初期化メソッドを呼び出します。❸self.param.trigger()
を使用して属性operator
のコールバックイベントをトリガーします。❹コールバックメソッドはデコレータparam.depends()
で定義され、ここではop1
、op2
、およびoperator
属性に対してコールバックメソッドrun()
を設定しています。パラメータwatch
がTrue
の場合にのみコールバックメソッド関数が追加されることに注意してください。param.depends()
で設定されたコールバックメソッドを呼び出す際、パラメータは渡されません。
❺Calculator2
のオブジェクトc2
を作成した後、その属性result
はすでに正しく割り当てられています。これは、初期化メソッドでself.param.trigger()
を使用してコールバックメソッド関数run()
を実行したためです。
class Calculator2(Calculator): # ❶
def __init__(self, **kw):
super().__init__(**kw) # ❷
self.param.trigger("operator") # ❸
@param.depends("op1", "op2", "operator", watch=True) # ❹
def run(self):
import operator as op
funcs = {"+": op.add, "-": op.sub, "*": op.mul, "/": op.truediv}
result = funcs[self.operator](self.op1, self.op2)
self.result = f"{self.op1:4.1f} {self.operator} {self.op2:4.2f} = {result:4.2f}"
c2 = Calculator2()
print(c2.result) # ❺
c2.op1 = 5
c2.operator = "*"
print(c2.result)
6.0 + 3.00 = 9.00
5.0 * 3.00 = 15.00
自動生成UI#
Parameterized
派生クラスは通常、ユーザーインターフェースを自動生成し、インターフェース内の各コントロールがトリガーするイベントに応答するために使用されます。まずPanelライブラリをロードし、extension()
を呼び出してNotebookで拡張プラグインを初期化します。Panelライブラリの関連章を参照して、PaneライブラリおよびそのJupyterLabプラグインを正しくインストールしてください。
import panel as pn
pn.extension(inline=False)
panel()
を使用してCalculator2
オブジェクトc2
のユーザーインターフェースを生成します。属性op1
とop2
はインターフェース内の2つのスライダーコントロールに対応します。これは、これらの属性に値の範囲があるためです。属性operator
はインターフェース内のドロップダウン選択ボックスに対応し、属性result
はテキスト入力ボックスに対応します。op1
、op2
、およびoperator
に対応するコントロールが変更されると、オブジェクトc2
内の対応する属性値も変更され、run()
メソッドが実行されます。run()
メソッドで属性result
が設定され、それに対応するテキスト入力ボックスのテキストも即座に更新されます。
p = pn.panel(c2, name="計算器")
p