ipywidgetsのDropdownやSliderで値を変えたときに関数を実行する

jupyterでウィジェット(ipywidgets)を使う記事の4記事目くらいです。1個は実例紹介みたいなやつなので使い方の記事としては3記事目になります。
1記事目: Jupyter Notebookでインタラクティブに関数を実行する
2記事目: Jupyter Notebook でボタンを使う

ボタンの使い方紹介したし他のUIも似たような感じで使えるやろって思い込んで放置していたのと、Sider等でぐりぐり操作したい場合は1記事目のinteractで十分なケースが多かったので触れてきませんでしたが、最近ある用途でipywidgets.IntSliderを使ったとき、思ったような動きをせずに苦戦しました。

先に結論を書いておくと、SliderやDropdownをインタラクティブに使いたいならobserveってメソッドに実行したい関数をセットし、names引数に”value”を渡して値の変更だけ監視するようにします。この記事ではDropdownとSlider (IntSlider/ FloatSlider) を例に取り上げますが、他のトグルボタンとかテキストボックス等でも事情は同じです。

さて、結論先に書いちゃいましたが自分が何に苦戦したのかを書いておきます。まず、Buttonを使うときは、インスタンスのon_clickメソッドにクリックしたときに実行したいメソッドを渡せば動作がセットされて、押すたびにそれが実行されるのでした。
なので、どうせSliderにはon_changeみたいなメソッドがあるんだろ、ってことで探すとon_trait_changeってメソッドが見つかります。で、これをやるとDeprecationWarningが出ます。今はobserveを使えということらしいです。

from ipywidgets import IntSlider
from IPython.display import display


def print_value():
    print(int_slider.value)


int_slider = IntSlider(min=0, max=100, step=10, value=50)
int_slider.on_trait_change(print_value)
display(int_slider)

# 以下出力される警告文
"""
/var/folders/g1/l4hsxb_54gsc0zgyczfb_xvm0000gn/T/ipykernel_1150/2385673427.py:9: DeprecationWarning: on_trait_change is deprecated in traitlets 4.1: use observe instead
  int_slider.on_trait_change(print_value)
"""

じゃぁ、observeを使うとどうなるかというと、次はスライダーを動かしたときにエラーが出ます。

def print_value():
    print(int_slider.value)


int_slider = IntSlider(min=0, max=100, step=10, value=50)
int_slider.observe(print_value)
display(int_slider)

# これでIntSliderは表示されるが、動かすと以下のエラーが出る
"""
TypeError: print_value() takes 0 positional arguments but 1 was given
"""

observeに渡すメソッドは引数を一個受け取るようです。ドキュメントを見ると変更に関する情報を関数に渡してくれるようですね。ありがたい。ちょっとその引数で渡される情報をprintするようにしてみましょう。

def print_value(change):
    print(change)


int_slider = IntSlider(min=0, max=100, step=10, value=50)
int_slider.observe(print_value)
display(int_slider)

これでSliderが表示されるのですが、値をちょっと変えると、なんかセットした関数(print_value)が3回実行されるのですよ。

ただ、chengeって引数にoldとnewってキーで新旧の値が入るのは便利ですね。ドキュメントを見ると、値が変わったときに一回だけ動かしたいなら、names=’value’って指定すると良いようです。上の画像で言うところの’name’: ‘_property_lock’ の変更はこれで出てこなくなります。

また、Sliderのような連続的に値を変えるUIは、例えば50から100へ値を変えようとすると途中の60,70,80なども通過します。ここで全部発火すると大変だ、最後に止まったところでだけ動いたらいい、と言う場合は、ウィジェットのインスタンス作るときにcontinuous_update=Falseを指定すると良いです。
結果コードは以下のようになります。

def print_value(change):
    print(change["old"], "から", change["new"], "に変化しました。")


int_slider = IntSlider(min=0, max=100, step=10, value=50, continuous_update=False)
int_slider.observe(print_value, names="value")
display(int_slider)

結果は省略しますが、Dropdownなどの他のウィジェットも同じようにして値の変化を検知できます。

from ipywidgets import Dropdown


drop_down = Dropdown(options=["high", "middle", "low"])
drop_down.observe(print_value, names="value")
display(drop_down)

Dropdownはvalueだけなく、labelやindexも変化するので、names=”value”を指定しない場合は、5回メソッドが実行されますね。用途によってはnames=”index”とか”label”などの方が使いやすい場面もあると思いますので確認しながら使ってみてください。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です