目次と前回の記事
これまでに作成したモジュール
以下のリンクから、これまでに作成したモジュールを見ることができます。
ルールベースの AI の一覧
ルールベースの AI の一覧については、下記の記事を参照して下さい。
〇×ゲームの GUI の入力機能
前回の記事では、〇×ゲームの ゲーム盤 を 画像で表示 するという、GUI の 出力機能 を 実装 しました。今回の記事では、GUI の 入力機能 を実装しますが、そのためには、〇×ゲームの 具体的 な 入力機能 について 検討 する 必要 が あります。まず、どのような入力機能が必要になるかについて少し考えてみて下さい。
〇×ゲームの GUI の入力機能の検討
〇×ゲームの 入力機能 について 最低限必要 になるのは、以下の機能 です。
- 着手 を行う 座標を入力 する 機能
他にも、必須ではありませんが、下記 のような 機能 があると 便利 でしょう。
- ゲーム を最初から やり直す 機能
- 直前の着手 を 取り消す、待った を行う機能
- 決着がついたゲームの 対戦経過 を 表示 する リプレイ の機能
本記事 では、上記の機能 を 実装 することにしますが、他にも、あると良さそうだと思った機能があれば列挙して実装してみて下さい。また、本記事でも上記以外の機能を思いついた場合は、実装することにします。
具体的な入力機能の検討
次に、それぞれ の 機能 について、具体的 にどのような方法で入力を行うについて 検討 を行う 必要 が あります。それぞれについて、少し考えてみて下さい。
着手を行う座標を入力する機能
CUI では、着手 を行う 座標 を、テキストボックス に座標を表す 文字列で入力 しました。GUI では、ゲーム盤 が 画像で表示 されるので、着手 を行いたい マス を 直接マウス で クリック するのが最も 直観的 で わかりやすく、入力の手間 も 簡単 だと思います。
他にも、メニュー から 座標を選択 するなどの 方法 も 考えられます。本記事よりも良い方法が思いついた方は、その方法で実装してみると良いでしょう。
この機能 を 実装 するためには、下記 のようなプログラムを 記述 する 必要 が あります。
-
ゲーム盤 の 画像の上 で クリック した時に、以下の処理 を 行う
- クリック した ゲーム盤 の マスの座標 を 知る
- その座標 に 着手 を 行う
- ゲーム盤 の 画像 を 更新 する
matplotlib は、上記 のような 処理 を 行うための機能 を 備えています が、残念ながら、JupyterLab 上で matplotlib を使って 画像を描画 した場合は、そのままでは その機能 を 利用 することは できません。具体的 には、JupyterLab で matplotlib の plt.show()
で 描画した画像 は、後から その 描画内容 を 変更 したり、クリック などの 操作 によって、特定の処理 を行うプログラムを 記述 することは できません。実際に、前回の記事で play
メソッドに〇×ゲームの 画像を描画 する 機能を実装 しましたが、その際には、着手 を 行うたび に、新しいゲーム盤の画像 を 描画 しました。
上記 のような 処理を行う ためには、今回の記事で紹介する、ipywidgets と ipympl などの モジュール を インストール して 利用 する 必要 が あります。
本記事では取り扱いませんが、JupyterLab 以外 の、Python の プログラムを実行 する 環境 で matplotlib を 実行 した場合や、JupyterLab で上記の ipywidgets と ipympl 以外 の モジュールを利用 した場合は、本記事 で紹介するプログラムとは 異なる記述 を 行う必要 が あります。
その他の機能
「ゲームを最初からやり直す」、「待ったを行う」、「リプレイを表示する」などの 機能 は、画面上 にそれらの 機能を呼び出す ための ボタン などを 描画 し、マウスで ボタンをクリック した際に その機能を実行 するという 方法 で 実装 するのが 一般的 です。
Python には、そういった GUI の アプリケーション を 作成 するための 様々なモジュール が あります が、本記事では、JupyterLab 上 で、ボタンやテキストボックスなどの GUI による 入力処理 を行うことができる ipywidgets という モジュール を 利用 することにします。
ipywidgets による GUI
ipywidgets は、IPython 上 で widgets を 利用 できるようにするための モジュール です。
IPython とは、Python の プログラム を、対話型に実行 するための シェル(プログラム)のことで、JupyterLab は ウェブブラウザで動作 するように IPython を拡張 したものです。
なお、Python のプログラムを 実行 するための シェル は、IPython 以外 にも あります が、IPython は それらの中 でも、便利な機能 が 豊富に用意 されているため、 良く使われます。
IPython のように、ユーザ と、コンピュータ の OS や Python などの プログラム言語 を つなぐ役割 を持つ プログラム の事を シェル と呼びます。
Windows の場合は、コマンドプロンプト や、Windows Power Shell が ユーザー と Windows の OS を つなぐ役割 を持つ シェル です。
JupyterLab は、セル に 入力 した プログラム を Python に伝え、その 実行結果 を 表示 するという形で ユーザ と Python を つなぐ処理 を 行います。
ウィジェット(widget)1とは、ボタン や テキストボックス などの、GUI を 構成する部品 のことを 表す用語 です。ipywidgets は、その名前が示すように、IPython 上で、ウィジェット を 扱う ことが できるようにする ための モジュール です。
ipywidget の詳細については、下記のリンク先を参照して下さい。
なお、ipywidget は非常に 多彩な機能 を持つ モジュール ですが、本記事 ではその中で、〇×ゲームの GUI を 作るために必要な機能 を 主に紹介 します。ipywidget は 有名なモジュール で、使い方 を 説明 する ウェブページ は 多数ある ので、他の機能について知りたい方は調べてみると良いでしょう。
ipywidgets のインストール
ipywidgets は、matplotlib と同様 の、下記の手順 で、Anaconda Navigator から インストール することが できます。
- Anaconda Navigator を立ち上げる
- 左の Environments を クリック して表示される 仮想環境の一覧 から marubatsu を クリック すると、右に marubatsu の 仮想環境 に インストール されている モジュールの一覧 が 表示 される
- 上のメニュー から「Not installed」を 選択 し、その右の「Search packages」と表示された テキストボックス に「ipywidget」を 入力 する
- 下に ipywidget が表示 されるので、その 左のチェックボックス を チェック し、下の「Apply」ボタン を クリック する。
- 「Install packages」という パネルが表示 され、しばらく待つと「Apply 」ボタン がクリックできるようになるので クリック する。
ipywidgets のインポート
ipywidgets を 利用 するためには、インポート を 行う必要 が あります。本記事では、下記のプログラムのように、widgets
という 名前 で インポート することにします。
import ipywidgets as widgets
モジュールのバージョン
本記事 では ipywidgets の バージョン 8.1.2 を インストール して 利用 します。モジュール の バージョンが異なる と、モジュールの機能 が 若干異なる 場合があります。本記事 の プログラムを実行 した際に、エラーが発生 したり、表示が異なる 場合は、ipywidgets の バージョンを確認 して下さい。例えば、7 以前 の バージョン では 利用できない機能 を使ってプログラムを 記述する可能性 が あります。
8.1.2 以降 のバージョンであれば、おそらく問題なく動作する と思います。
モジュールのバージョンの確認方法
モジュール の バージョン を 確認する方法 をいくつか 紹介 します。
ほとんどのモジュール では、__version__
という 名前 の 特殊属性 に、モジュール の バージョン を 表す文字列 が 代入 されています。従って、下記 のプログラムのように、モジュールの __version__
属性 を 表示 することで バージョン を 確認 することが できます。
print(widgets.__version__)
実行結果
8.1.2
Anaconda Navigator から、下記の手順 で、仮想環境 に インストール された モジュール の バージョン を 確認 することが できます。
- Anaconda Navigator を立ち上げる
- 左の Environments を クリック して表示される 仮想環境の一覧 から marubatsu を クリック すると、右に marubatsu の 仮想環境 に インストール されている モジュールの一覧 が 表示 される
- 上のメニュー から「Installed」を 選択 し、右の「Search packages」と表示された テキストボックス にモジュール名を 入力 すると、下図のように バージョンが表示 される
モジュールのバージョンの変更方法
何らかの理由で、インストールされている モジュール の バージョン を 変更 したい場合は、Anaconda Navigator を使って 下記の手順 で 変更 することが できます。
1. 上記の画面で、左にある 緑色 の チェックマーク を クリック すると、下図 のような メニューが表示 される
2. 「Mark for specific version installation」を クリック すると、下図 のような、バージョン を 選択 する メニューが表示 される
3. バージョンを選択 し、下記の Apply ボタン を クリック する。「Install packages」という パネルが表示 され、しばらく待つと「Apply 」ボタン がクリックできるようになるので クリック する
本記事では説明しませんが、conda や pip などの コマンド を使って、異なるバージョン を インストールし直す ことが できます。
現在よりも 新しいバージョン を インストール することを「バージョンアップ」、古いバージョン を インストール することを「バージョンダウン」と呼びます。
イベント駆動型プログラミング
これまで に 本記事 で 記述 してきた プログラム は、実行 すると 記述 した プログラムの処理 が 順番に行われ、すべての処理 が 終了 した時点で、プログラムの実行 も 終了 します。そのような プログラミングの手法 の事を、処理の流れ(flow)を 記述 して 実行 することから、フロー駆動型プログラミング と呼びます。
それに対して、ボタン や メニュー などによって 操作 を行う GUI の アプリケーション では、一般的 に プログラムの実行後 に、ボタン などの GUI を操作するまで の間は 待機し、ボタン などの GUI に対するの 操作 が 行われた時 に 処理が行われる ようになっています。そのようなプログラムは、フロー駆動型プログラミング の 手法で記述 することは 困難 なので、GUI の アプリケーション は、イベント駆動型プログラミング という 手法 が 良く使われます。また、本記事 で 利用 する、ipywidgets は その手法 を 用います。
イベント駆動型プログラミングの用語
イベント駆動型プログラミング で使われる 主な用語 について最初に 説明 します。
用語 | 意味 |
---|---|
ウィジェット | ボタンやテキストボックスなどの、GUI を 構成する部品 コントロール などと 呼ぶ場合 もある |
イベント | ウィジェットに 対して行われる 操作 |
イベントの発生 | ウィジェットに 対して 操作が行われたこと を表す |
イベントハンドラ | イベントの発生 に 応じた処理 を行う プログラム |
イベントループ | イベントの発生 に 応じて、適切 な イベントハンドラ を 実行する処理 を 行うプログラム |
イベント駆動型プログラミングによる GUI アプリの記述方法
イベント駆動型プログラミング の 手法 を 利用 した GUI の アプリケーション は、下記 のような 手順 で プログラムを記述 します。
- 画面内 の適切な位置に ウィジェットを配置 する 処理を記述 する
-
それぞれ の ウィジェット に対して、下記の処理 を 記述 する
- ウィジェット を 操作 した際に 行う処理 を 記述 する、イベントハンドラ というプログラムを 記述 する
- ウィジェット に イベントハンドラ を 結び付ける
具体例 として、ipywidgets で、ボタンをクリック すると、画面 に メッセージを表示 する GUI のプログラムを 記述 する 方法 を 紹介 します。
ボタンのウィジェットを作成して配置する
ipywidgets には 様々な種類 の ウィジェット を 作成 し、JupterLab 上 に 表示 する 機能 があり、ウィジェット の 種類ごと に、ウィジェットを作成 する 関数が用意 されています。例えば、Button
という 関数 によって、ボタン の ウィジェット を 作成 することが できます。Button
の 返り値 は、作成 された ウィジェット を表す オブジェクト です。
作成 した ウィジェット は、display
という 関数 を使って JupyterLab に 描画 することが できます。これまでに利用 してきた print
は、文字列を表示 する 機能 を持ちますが、display
は、画像 や ウィジェットなど を JupyterLab に 描画 する 機能 を持つ 関数 です。
下記 は、ボタン の ウィジェット を 作成 して 描画 するプログラムです。なお、キーワード引数 description
は、ボタン に 表示 する 文字列 を 設定 する 実引数 です。
button = widgets.Button(description='Click me')
display(button)
実行結果(下図は、画像なので操作することはできません)
Quiita の 記事内 で Python のプログラムを 実行 することは できない ので、本記事内 の 実行結果 には、ウィジェット の 画像 を表示します。実際のウィジェット は、JupyterLab 上で 確認して下さい。
display
は、IPython という モジュール で 定義 された 関数 ですが、VSCode の JupyterLab では、IPython モジュール を インポートしなくても利用できる ようです。なお、display
を実行 して エラーが発生 する場合は、下記 のプログラムを 記述 して display
を インポート して下さい。
from IPython.display import display
表示 された ボタン は、一般的 なアプリケーションの ボタンと同様 に、マウス で クリック することで ボタンの色 が 一時的に変化 しますが、ボタン を クリックした時 の 処理 をまだ 記述していない ので、クリック しても 色が変わるだけ で 他の処理 はまだ 行われません。
Button
の 詳細 については、下記のリンク先を参照して下さい。
ipywidgets の ウィジェットの種類 については、下記のリンク先を参照して下さい。
イベントハンドラの定義
ipywidgets では、イベントハンドラ は イベント(event)を 処理 する(handle)機能を持つ関数 として 定義 します。今回のプログラムでは、ボタン を クリック するという 操作 を 行った時 に メッセージを表示 するという 処理を行う ので、その処理 を 行う ための イベントハンドラ を 定義 する 必要 が あります。
ipywidgets では、ウィジェットの種類 によって、イベントハンドラ の 定義の方法 が 若干異なります。ボタン の ウィジェット に対する イベントハンドラ は、仮引数 を 1 つ持つ関数 として 定義 します。その仮引数 には、イベントハンドラ が 呼び出された際 に、ボタン の ウィジェット を表す オブジェクトが代入 されます。
この後で紹介する例を含め、ボタン に対する イベントハンドラ の 仮引数の情報 が 必要になる ことは あまりない かもしれませんが、仮引数 を 記述せず に イベントハンドラを定義 すると エラーが発生 するので、必ず記述 する 必要 が あります。
ボタン以外 の ウィジェット の イベントハンドラ の 仮引数 には、別の情報 が 代入 されます。具体例は今後の記事で紹介する予定です。
下記 は、画面 に "ボタンがおされたよ!" という メッセージを表示 する イベントハンドラ の 定義 の一例です。イベントハンドラ の 関数 の 名前 は、ボタン(button)が クリックされた時(on clicked)に 行う処理 なので on_button_clicked
に、仮引数 の 名前 は、ボタン の ウィジェットの情報 が 代入 されるので button の 頭文字 をとって b
としました。なお、これらの名前 に 決まりはない ので 好きな名前 に 変更 しても かまいません。
def on_button_clicked(b):
print("ボタンがおされたよ!")
ウィジェットへのイベントハンドラの結び付け
イベントハンドラ を 定義しただけ では、ボタン を クリック しても 何も起きません。ボタン を クリック した際に イベントハンドラを実行する ようにするためには、ウィジェット と イベントハンドラ を 結び付ける という 処理を記述 する 必要 が あります。
ボタン の ウィジェット を表す オブジェクト には、on_click
という、ウィジェット と イベントハンドラ を 結び付ける機能 を持つ メソッド が用意されており、下記 のプログラムのように、実引数 に イベントハンドラ を 記述 して呼び出すことで、ボタン を クリックした際 に、そのイベントハンドラ が 呼び出される ようになります。
button.on_click(on_button_clicked)
上記 のプログラムを 実行後 に、先程表示 した ボタン を クリック すると、クリックするたび に、ボタンの下 に 下図 のように メッセージが表示 されるようになります。なお、下図 は 3 回クリック を行った 場合の図 です。
on_click
メソッドは、ボタン の ウィジェットのみ で 利用 できます。他のウィジェット に イベントハンドラ を 結び付ける 場合は observe
という メソッド を 利用 します。具体的な方法については、今後の記事で紹介する予定です。
上記 のプログラムでは ボタンの下 に メッセージが表示 されますが、メッセージ を 別の場所 に 表示 することも できます。具体的な方法は今後の記事で紹介します。
ipywidgets の ボタン の widgets を 利用 することで、〇×ゲームの リセットなど の 機能 を 実装 することが できます。具体的な実装は次回の記事で行います。
イベント駆動型プログラミングの仕組み
下記は、これまで の、フロー駆動型プログラミング と、イベント駆動型プログラミング の 違い を表す表です。フロー駆動型 の場合は、プログラムの実行 が 終了した時点 で、プログラムの処理 が すべて完了 します。ここでいう プログラム の 実行の終了 とは、JupyterLab の セルに入力 した プログラムの実行 が 終了 し、次のプログラム を セルに入力 して 実行できる状態になる という 意味 です。
駆動型 | 性質 |
---|---|
フロー | プログラムの実行 が 終了した時点 で、プログラムの処理 が 完了する |
イベント | プログラムの実行 が 終了した後 でも、プログラムの処理 は 完了しない |
イベント駆動型 の場合は、プログラムの実行 が 終了した後 も、この後で説明する、イベントループ という 処理 が 引き続き実行 されるため、プログラムの処理 は 完了しません。
イベントループ は、下記 のような アルゴリズム で 処理 を行う プログラム のことです。イベントループ は、「イベント が 発生するまで待ち、発生したイベント に 対する処理 を 行う」という処理を 無限に繰り返す(ループ する)ことが、名前の由来 になっています。
- ipywidgets に 登録 された イベントハンドラ に 対応するイベント が 発生しているか どうかを 調べる
- イベント が 発生していれば、対応 する イベントハンドラ を 呼び出す。発生していなけれ ば 何もしない
- 手順 1 へ 戻る
ipywidgets を インポート すると、自動的 に 上記 の イベントループ の 処理 を行うプログラムが 実行 されます。その際 に、JupyterLab の セル に プログラム を 入力 して 実行できる ので、イベントループ の 処理 が 行われている ことが 実感できない かもしれませんが、実際 に、見えない所 で イベントループの処理 が 行われています。
最初 は、ipywidgets に イベントハンドラ は一つも 登録されていない ので、イベントループ は、実質的 に 何の処理 も 行いません。
先程のように、ボタンなどの ウィジェットを作成 し、イベントハンドラ と 結びつける ことで、ipywidgets に イベントハンドラが登録 され、以後 は、イベントハンドラ に 対応するイベント が 発生するたび に、イベントループの処理 によって、イベントハンドラが実行される ようになります。また、対応するイベント が 発生しない間 は、イベントループ は 実質的 に 何の処理 も 行わない ので、イベント が 発生するまでの間 は 待機状態 になります。
これが、イベント駆動型プログラミング の 大まかな仕組み です。
実際 の イベントループ は、上記以外 の 処理 も 行っています が、イベント駆動型プログラミング の おおまかな仕組み を 把握するだけ であれば、上記の処理 を 理解 するだけで 十分 でしょう。
イベントループと play
メソッドのループの類似点
イベントループ が 行う処理 が ピンとこない人 が 多いのではないか と思いますので、Marubatsu
クラスの play
メソッドで行う 処理 が、イベントループ で行われる 処理 と 似ている ことを 説明 します。
イベントループ を、Python 風 の プログラム で 記述 すると 以下 のようになります。
while True
if ipywidgets に登録されたイベントハンドラに対応するイベントが発生している?:
対応するイベントハンドラを呼び出す
下記 は play
メソッドの中で、人間どうし が 対戦 を行った際に、イベントループ に 似た処理 を行う 部分 を 抜粋 したプログラムです。
# ゲームの決着がつくまで無限に繰り返す
while self.status == Marubatsu.PLAYING:
# キーボードからの座標の入力
coord = input("x,y の形式で座標を入力して下さい。exit を入力すると終了します")
# この後で、coord に着手を行うプログラムが記述されている
上記 のプログラムは、下記 の点で イベントループ と 似ています。
-
input
という 組み込み関数 が 行う処理 は、テキストボックス への 文字列の入力 という イベントが発生 するまで 待機 する - 文字列 が 入力された場合 は、入力された 文字列 に 対応する処理 を 行う
- 処理 の 完了後 は、再び 次の入力を待つ という 処理 を 繰り返す
ただし、下記 の点では、イベントループ とは 異なります。
-
play
メソッドの場合は、ゲームの 決着がついた場合 は 繰り返しを終了 するので、イベントループと異なり、無限ループではない -
イベントループ では、複数 の イベントハンドラ を 登録 することで、複数 の イベント に 対応する処理を行う ことが できるが、
play
メソッドの場合は、テキストボックス への 文字列の入力 という、一つのイベント にしか 対応できない
このように、play
メソッドが 行う処理 は、イベントループが行う処理と 異なる部分もありますが、「イベント が 発生するまで待ち、発生した場合 に 対応する処理を行う」という 点 では、同様の処理 を 行っています。
初心者の方 には 理解しづらい かもしれないので、上記の違い と、下記の説明 については、現時点 では よくわからなくても構わない と思います。
play
メソッドが、イベントループ と 異なり、一つのイベント にしか 対応できない理由 は、input
を 実行 すると、テキストボックス に 文字列 が 入力されるまで は、他の処理 を 一切行わない からです。このように、実行 すると、特定の条件 が 満たされるまで 処理が 終了しない ことを、処理 が ブロックされる と呼びます。
イベントループ では、特定のイベント が 発生 するまで、処理 を ブロックする といことは 行っていない ため、複数の種類 の イベント に 対応 することが できます。
なお、play
メソッド は、そもそも 文字列を入力 する 以外 のイベントに 対応する必要はない ので、そのまま でも 問題はありません。
ipympl
matplotlib には、描画 した 画像 に対して、下記のような インタラクティブ2(interactive)な 処理を行う プログラムを 記述するため の 機能 があります。しかし、JupyterLab では、そのままでは matplotlib の 下記の機能 を 利用 することは できません。
- 描画した画像 を、後から更新 する
- 描画した画像 に対する、マウスを押す などの 操作 に 対応する処理 を表す イベントハンドラー を定義し、結び付ける ことが できる
ipympl3 は、JupyterLab 上で、matplotlib の インタラクティブな機能 を 利用できるよう にする モジュール です。
ipympl の詳細については、下記のリンク先を参照して下さい。
ipympl のインストール
ipympl も、Anaconda Navigator を使って、下記 の方法で インストール できます。
- Anaconda Navigator を立ち上げる
- 左の Environments を クリック して表示される 仮想環境の一覧 から marubatsu を クリック すると、右に marubatsu の 仮想環境 に インストール されている モジュールの一覧 が 表示 される
- 上のメニュー から「Not installed」を 選択 し、その右の「Search packages」と表示された テキストボックス に「ipympl」を 入力 する
- 下に ipympl が表示 されるので、その 左のチェックボックス を チェック し、下の「Apply」ボタン を クリック する。
- 「Install packages」という パネルが表示 され、しばらく待つと「Apply 」ボタン がクリックできるようになるので クリック する。
ipympl のバージョン
ipympl を 利用する際 は、matplotlib の バージョン の 組み合わせの制限 があり、制限を満たさない 場合は エラーが発生 したりしてプログラムが うまく動作しない 場合が あります。
具体的 な 組み合わせ については、下記のリンク先 を 参照 して下さい。
筆者の PC では、下記のバージョン で 動作を確認 しています。プログラムが うまく動作しない場合 は、先程説明 した方法で モジュール の バージョン を 確認 し、必要 であれば バージョン を あわせてみて 下さい。
モジュール | バージョン |
---|---|
ipympl | 0.9.3 |
matplotlib | 3.8.0 |
上記 の リンク先 の jupyter-matplotlib と JupyterLab の バージョン についてですが、VSCode の JupyterLab を 利用 する場合は、VSCode の 拡張機能 の Jupyter が関係します。下図 は、筆者 の VSCode の 拡張機能 の Jupyter の表示 です。
実はこの記事を最初に執筆した時に、ipympl を 利用 すると、matplotlib の 画像 が うまく表示できない という 問題 が 発生 しました。試行錯誤した結果、筆者 の VSCode の Jupyter が 下図 のように、上部 に pre-release が 表示 される プレリリースバージョン になっており、下図 の「リリースバージョンへの切り替え」をクリックして、VSCode を 再起動 した結果、画像 が うまく表示される ようになりました。
ただし、筆者 の PC で うまく画像 が 描画できなかった理由 が、プレリリースバージョン の せいか どうかは よくわかりません。もしかすると、単にVSCode にインストールされていた Jupyter の拡張機能のバージョンが古く、リリースバージョンへの切り替えをクリックしたことでバージョンアップされたことで問題が解決できただけかもしれません。いずれにせよ、この後のプログラムを実行した際に、matplotlib の 画像 が うまく描画できない 場合は、VSCode の 拡張機能 の Jupyter をみて、バージョンアップできる のであれば、しておくこと を お勧めします。うまくいかない方 や、この件 について ご存じの方 は コメント に 書いていただけると嬉しい です。
ipympl の利用方法
ipympl を 利用する際 は、これまでのように モジュール を インポート するの ではなく、下記の マジックコマンド を 記述 して 実行する必要 があります。
%matplotlib widget
上記の % で 始まるもの は、JupyterLab の 機能を呼び出す、マジックコマンド と呼ばれるもので、Python の プログラム では ありません。マジックコマンド は、JupyterLab の セルの中 で のみ実行 できるため、marubatsu.py
などの、モジュールの中 に 記述 すると エラーが発生 する点に 注意 して下さい。
本記事では、他のマジックコマンドについては、必要に応じて紹介する予定ですが、興味がある方は、「Jupyter マジックコマンド」をキーワードに検索してみると良いでしょう。
%matplotlib widget
の効果
%matplotlib widget
を 実行 することで、matplotlib に 対する処理 が、大きく変化 します。その 変化の内容 について 説明 します。
インタラクティブモード
matplotlib には、matplotlib の インタラクティブな機能 を 利用できる「インタラクティブモード(interactive mode)」と、利用できない「非インタラクティブモード(non interactive mode)」があります。JupyterLab では、matplotlib は 初期設定 では、非インタラクティブモード になっていますが、%matplotlib widget
を 実行 することで、matplotlib が インタラクティブモード に なります。
インタラクティブモード では、matplotlib での 画像の描画 の 処理 が、以下 のように、これまでの 非インタラクティブモード と 大きく異なる処理 が行われるようになります。
- 下記のような、Figure に 対する変更 が 行われるたび に、画像の描画 が 更新 される
- Figure の作成
-
plot
メソッドなどによる Artist の登録 -
set_xlim
メソッドなどによる Artist の 設定の変更
それ以外の効果
%matplotlib widget
を 実行 すると、上記に加え、下記 のような matplotlib で 描画した画像 を インタラクティブ にする 効果 が 得られます。
- 描画 した 画像の付近 に、Figure の タイトル、マウスの座標、画像に対する操作 を行うための ツールバー などが表示されるようになる
- 描画 した 画像 に 対する マウスのクリックなどの イベント と、イベントハンドラ を 結び付ける ことが できる ようになる
-
JupyterLab の セルを実行 しても、
plt.show()
が自動的に 実行されなくなる -
plt.show()
を 実行 しても、pyplot が 管理 する Figure が 削除されない
上記 は、matplotlib の インタラクティブモード とは 関係 は ありません。
例えば、pyplot には、インタラクティブモード を 有効 にする ion
というメソッドがありますが、plt.ion()
を 実行 して インタラクティブモード にしても、JupyterLab で matplotlib を 利用する場合 は、それだけ では matplotlib の インタラクティブな機能 を 利用 することは できません。
上記の変化 について、具体例 をいくつか挙げて 説明 します。
画像の描画
%matplotlib widgets
を 実行後 は、下記 のプログラムのように、Figure を 作成するだけ で、画像が描画される ようになります。
import matplotlib.pyplot as plt
import japanize_matplotlib
fig = plt.figure(figsize=[3, 3], facecolor="lightblue")
実行結果(下図は、画像なので操作することはできません)
以前の記事で説明したように、%matplotlib widgets
を 実行しない 場合は、上記 のプログラムを 実行 しても 画像 は 描画されません。
また、描画される画像 は、%matplotlib widgets
を 実行しない 場合と 比べて、下記 のような 違い があります。それぞれについて説明します。
- 画像の上部 に、Figure の タイトル が表示される
- 画像の下部 に、Figure に 登録 された Axes に対する マウスポインタ の 座標が表示 される(上記 の例では Axes は 登録されていない ので 表示されない)
- 画像の右下 に、画像 の 大きさ を ドラッグ して 変更できる ことを表す 三角形のマーク が 表示 される
- 画像の上 に マウスを移動 すると、画像 を 操作 するための ツールバー が 表示 される
Figure のタイトルとマウスの座標
先程の 画像の上部 に 表示 される Figure 1 は、Figure の タイトル です。Figure の タイトル は、Figure を 作成した順番 で、Figure 1 のように 自動的 に 付けられます。
Figure に Axes が 登録されている 場合は、マウス を Axes の plot の上 に 移動 することで、画像の下部 に Axes に対する マウスポインタ の 座標が表示 されるようになります。なお、Figure の外 や、Figure の中で、Axes の範囲外 に マウスを移動 した場合は、何も表示されなくなります。
下記は、subplots
メソッドで、Axes が 登録 された Figure を 作成 するプログラムです。先程の図 と 区別 できるように、Figure の 背景色 を 黄色 にしました。実行結果 は、この 画像の上 に マウスを移動 した場合の図で、下部の表示 は Axes の (0.490, 0.371)A の 座標の上 に マウスポインタ が 存在する ことを 表します。なお、2 つ目 に 作成 した 画像 なので、Figure の タイトル は Figure 2 になります。
fig2, ax2 = plt.subplots(figsize=[3, 3], facecolor="yellow")
実行結果(下図は、画像なので操作することはできません)
なお、画像の左 に 表示 される ボタン は、この後で説明する ツールバー です。
Figure のタイトルの設定方法
Figure の タイトル を 自分で設定 したい場合は、下記 のプログラムのように、figure
メソッドや、subplots
メソッドで Figure を作成 する際に、キーワード引数 num
に タイトルを表す文字列 を 代入 して 呼び出す ことで行えます。実行結果 から、画像の上部 に 〇×ゲーム が 表示 されることが 確認 できます。
fig3, ax3 = plt.subplots(num="〇×ゲーム", figsize=[3, 3], facecolor="lightgreen")
実行結果(下図は、画像なので操作することはできません)
figure
メソッドや、subplots
メソッドの キーワード引数 num
は、画像の上部のタイトル以外にも、作成した Figure の 識別子 としても 利用 されます。
詳細は長くなるので、下記のリンク先を参照して下さい。
画像の大きさの変更と、ツールバー
画像 の 右下 の 三角形のマーク を マウスでドラッグ することで、画像の大きさ を 変更 することが できます。また、画像の上 に マウスを移動 することで、下図(先ほどの fig2
の画像です)のような ツールバー が 画像の内部 に表示されます。
この ツールバー は、画像 の 表示範囲の変更 や、ファイルへの保存 などを行うことができる ボタン ですが、〇×ゲーム では 利用しない ので本記事では 詳細 は 説明しません。興味がある方は、下記のリンク先を参照して下さい。
タイトルやツールバーなどに関する設定
Figure には、JupyterLab 上に 描画 される 画像 を表す オブジェクト が 代入 されている canvas
属性 があり、canvas
属性 に 代入 された オブジェクト には、画像の描画 の 設定 に関する 下記 のような 属性が存在 します。これらの属性の値 を 変更 することで、タイトル や ツールバー の 表示の有無 などの 設定 を 変更 することが できます。
属性名 | 意味 |
---|---|
header_visible 4
|
True の場合に上部(header)の タイトルを表示 する |
footer_visible 5
|
True の場合に下部(footer)の マウスの座標を表示 する |
toolbar_visible |
True の場合に ツールバーを表示 する |
resizable 6
|
True の場合に、画像のサイズ を 変形できる
|
下記 は、先程 最後に作成 した fig3
に対して、上記 の すべて を False
に 設定 するプログラムです。下記 のプログラムを実行すると、実行結果 のように、先程作成 した 画像 の タイトルなど が 表示されなく なります。また、マウスのドラッグ による 画像 の サイズの変更 を行うことも できなくなります。JupterLab 上で、実際に確認 してみて下さい。
fig3.canvas.toolbar_visible = False
fig3.canvas.footer_visible = False
fig3.canvas.header_visible = False
fig3.canvas.resizable = False
実行結果(下図は、画像なので操作することはできません)
plt.show()
に関する注意点
%matplotlib widget
を 実行 した場合は、以下 のような理由で、plt.show()
は基本的に 実行しません。2 つ目 の 現象 に関しては、この後 で 説明 します。
-
plt.show()
を 実行しなくても、Figure を 作成 したり、Figure の 画像の内容 が 変化 するような 処理を行う と、自動的 に 画像の描画 が 更新される -
plt.show()
を 実行 すると、同じ画像 が 同時 に 複数描画 されてしまう
plt.show()
の処理の変化
上記で説明したように、%matplotlib widget
を 実行 した場合は、基本的に plt.show()
を 実行しない ので、下記の説明 は、現時点 では 理解する必要 は ありません。意味が分からない方や、あまり興味がない方は、この後の 画像の描画の更新 に 進んで下さい。
%matplotlib widget
を 実行 している 場合 と、そうでない場合 は、plt.show()
メソッドを 実行 した際の 処理 が 下記 のように 変化します。
%matplotlib widget |
plt.show() に関する処理 |
---|---|
実行していない |
pyplot が 管理 する すべての Figure を 描画 する pyplot が 管理 する Figure をすべて 削除 する JupyterLab の セルを実行 すると、 plt.show() が 実行される
|
実行した |
current Figure のみ を 新しく描画 する pyplot が 管理 する Figure は 変化しない JupyterLab の セルを実行 しても、 plt.show() は 実行されない
|
pyplot が管理する Figure と current Figure
上記 のようなことが起きることを 確認するため には、pyplot が Figure を 管理する仕組み について 理解 する 必要 が あります。
pyplot は、figure
メソッドや subplots
メソッドで 作成 した Figure を 管理 する 機能 を持っています。pyplot が 管理 している Figure の 一覧 は、下記 のプログラムのように、pyplot の get_figures
メソッドで 知る ことが できます。このメソッド の 返り値 は、Figure に 割り当てられた ID の 番号 を 要素 とする list で、先程 3 つ の Figure を作成 したので、実行結果 には 3 つ の Figure に 対応 する ID が表示 されます。
plt.get_fignums()
実行結果
[1, 2, 3]
pyplot が、管理 する Figure の中で、pyplot が 現在(current)処理の対象 とする Figure を current Figure と呼びます7。pyplot では、新しい Figure を 作成 すると、その Figure が current Figure になります。
本記事では利用しませんが、pyplot には current Figure と current Axes が 何であるか を 調べたり、別のもの に 変更 する メソッド があります。
%matplotlib widget
を実行した場合の plt.show()
の処理の確認
下記 のプログラムを実行すると、実行 した セルの下 に、current Figure である、先程作成した、3 つ目 の 画像のみ が 描画 されます。
plt.show()
実行結果(下図は、画像なので操作することはできません)
また、下記 のプログラムを実行することで、plt.show()
を 実行 しても、pyplot が 管理 する Figure が 変化していない ことが 確認 できます。
plt.get_fignums()
実行結果
[1, 2, 3]
上記 のプログラムを 実行 した際に、実行結果 に 画像が描画されない点 に 注目 して下さい。もし、JupyterLab の セルを実行 した際に、自動的 に plt.show()
が実行される のであれば、先程 plt.show()
を 実行した場合 と 同様 に、セルの下 に 画像が描画 されるはずです。また、下記 のプログラムのように、もう一度 plt.show()
を 実行 した場合は、fig3
の 画像 が 新しく描画 されます。
このことから、%matplotlib widget
を 実行 している 場合 は、セルを実行 しても 自動的 に plt.show()
が 実行されなくなっている ことが 確認 できました。
plt.show()
実行結果(下図は、画像なので操作することはできません)
%matplotlib widget
を実行していない場合の plt.show()
の処理の確認
次に、下記 のプログラムを 実行 して下さい。%matplotlib inline
は、%matplotlib widget
を 実行する前 の 状態に戻すマジックコマンド です。
%matplotlib inline
上記を実行 した 後 で、下記 のプログラムのように、plt.show()
を 実行 すると、前回までの記事のように、pyplot が 管理 する 3 つの Figure が すべて セルの下に 描画されます。また、描画 される 画像 は、下記の点 が 先程 と 異なります。
- 1 つ目 の Figure には、Axes が 登録されていない ので、画像 は 描画されない
- いずれの画像 にも、タイトル や ツールバー は 表示されず、変形もできない
plt.show()
実行結果
<Figure size 450x450 with 0 Axes>
また、下記 のプログラムを実行することで、plt.show()
によって、pyplot が 管理 する Figure が 削除 されたことが 確認 できます。
plt.get_fignums()
実行結果
[]
画像の描画の更新
この前 の「plt.show()
の処理の変化」で 記述 されている プログラム を 実行した人 と、この前の説明を 読み飛ばして来た人 では、JupyterLab の 状態が異なる ので、JupyterLab を 再起動 してから、下記 のプログラムを 実行 して 新しい Figure を 作成 して下さい。
%matplotlib widget
import matplotlib.pyplot as plt
import japanize_matplotlib
fig, ax = plt.subplots(figsize=[3, 3])
実行結果(下図は、画像なので操作することはできません)
下記は、上記 で 描画 した Figure の Axes に対して、plot
メソッドで 線を描画 するプログラムです。下記 を 実行 すると、これまでと異なり、下記 のプログラムの セルの直後 には 画像 が 描画されず、上記 で 描画 した 画像 が 更新されて、直線が描画されます。
ax.plot([1, 2], [1, 2])
実行結果(先程描画した画像が下記のように更新されます)
このように、%matplotlib widget
を 実行 した場合に、既に作成した画像 に対して 描画などの処理 を行うと、新しい画像が作成 されるの ではなく、既に描画した画像 が 更新される ことになります。また、先程 fig3
の タイトル や ツールバー の 表示の設定 を 修正 した際に、既に描画 した fig3
の 画像 の 表示の設定 が 変化 したのは、そのため です。
本記事 の 最初で決めた、〇×ゲーム の 以下の処理 は、%matplotlib widget
を 実行 することで 記述できる ようになります。
- 着手を行う ことで、ゲーム盤 の 画像の内容 を 更新 する
plot.show()
を実行すべきでない理由
先程、%matplotlib widget
を 実行 した場合は、基本的 に plt.show()
を 実行しない と説明しましたが、その 理由の一つ を 紹介 します。
下記 は、plt.show()
を 実行 し、セルの下 に 先程の画像 を もう一つ描画 するプログラムです。実行結果 から、先程と同じ画像 が 描画 されることが 確認 できます。
実行結果(下図は、画像なので操作することはできません)
plt.show()
続けて、下記 のプログラムを 実行 して、画像 に もう一本の線 を 描画 すると、先程描画した 2 つの画像 の 両方 に 線が描画 されることになります。
ax.plot([1, 2], [2, 1])
実行結果(下図は、画像なので操作することはできません)
画像の内容 を 更新 する際に、このように 同じ画像 を 2 つ描画 して 同時に更新 する 必要 は 一般的 には ありません。これが、plt.show()
を 実行しない理由 の一つです。
matplotlib の Figure に対するイベントハンドラの登録
今回の記事 の 最初 で、〇×ゲーム の 着手を入力 する GUI を 実装 するためには、下記 のようなプログラムを 記述 する 必要がある と説明しました。
-
ゲーム盤 の 画像の上 で クリック した時に、以下の処理 を行う
- クリック した ゲーム盤 の マスの座標 を 知る
- その座標 に 着手を行う
上記の処理 を 実現 するためには、matplotlib で 描画 した 画像の上 で マウスがクリック された際に 実行 される、イベントハンドラ を 定義 し、その イベントハンドラ を、画像 に 結び付ける必要 が あります。
mpl_connect
メソッド
先程、Figure には、JupyterLab 上に 描画 される 画像 を表す オブジェクトが代入 されている canvas
属性 があると説明し、そのオブジェクト の いくつかの属性 を 紹介 しました。
canvas
属性 に代入された オブジェクト には、mpl_connect
という、JupyterLab 上に 描画 された matplotlib の 画像 に対する イベント と イベントハンドラ を 結び付け(connect)、ipywidgets に 登録 することで、イベントループ で 処理を行う ことができるようする メソッド があります。mpl_connect
を 実行する際 には、イベントの種類 を表す 文字列 と、イベントハンドラ の 2 つ の 実引数 を 記述 します。
イベントハンドラの定義
mpl_connect
で結びつける イベントハンドラ は、仮引数 を 1 つ持つ関数 として 定義 する 必要があります。また、その仮引数 には、発生 した イベントの情報 を表す オブジェクトが代入 されます。なお、仮引数 の 名前 は どのような名前 を付けても 構いません が、一般的 には、event
、evt
、e
などが 良く使われます。
下記は、仮引数 に 代入 された オブジェクト の 主な属性 の 一覧 です。
属性名 | 意味 |
---|---|
inaxes |
マウス が Axes の上にあるか どうかを表す bool 型 のデータ |
xdata 、ydata
|
マウス が Axes の上 に ある場合 は、その Axes 上 の x 座標 と y 座標 マウス が Axes の上 に ない場合 はどちらも None が代入 される |
x 、y
|
Figure の 左上の座標 を 原点 とした場合の マウス の x 座標 と y 座標 |
button |
マウス の ボタン が 押された場合 の、ボタンの種類 |
key |
キー が 押された場合 の、キーの種類 |
イベントの属性の詳細は、下記のリンク先を参照して下さい。
下記 は、イベント が 発生した際 の、Axes 上 の マウスの座標 を 表示 する イベントハンドラ の 定義 を行うプログラムです。マウス(mouse)が押された(down)際(on)の イベントハンドラ を 想定 して、名前 は on_mouse_down
としました。
def on_mouse_down(event):
print(event.xdata, event.ydata)
mpl_connect
の記述方法
matplotlib の 画像 には、マウスを押す、キーを押す などの、様々な種類 の イベント に対して、それぞれ個別 に イベントハンドラ を 定義 して 結びつける ことが できます。
先程紹介した ipywidgets の ボタン などの ウィジェット に対して イベントハンドラ を 結び付ける方法 は、matplotlib の 画像 に対して イベントハンドラ を 結び付ける方法 とは 異なる ので、混同しない ように 注意 して下さい。
mpl_connect
は、実引数 に、イベントの種類 と、イベントハンドラ を 記述 することで、指定 した イベントが発生 した際に、指定 した イベントハンドラ が 呼び出され て 実行される ようになります。主なイベントの種類 には、以下 のようなものがあります。
イベントを表す文字列 | イベントの意味 |
---|---|
"button_press_event" |
マウス の ボタン を 押した |
"button_release_event" |
マウス の ボタン を 離した |
"key_press_event" |
キー を 押した |
"key_release_event" |
キー を 離した |
"figure_enter_event" |
Figure の中 に マウスが移動 した |
"figure_leave_event" |
Figure の外 に マウスが移動 した |
イベントの種類の一覧と詳細については、下記のリンク先を参照して下さい。
上記の表から わかるように、マウス の クリックに対応 する イベント は 存在しません。マウス の クリック は、マウス の ボタン を 押して、同じ場所 で 離す という 操作 なので、マウス の クリック に 対応する処理 を 記述 したい場合は、以下 のようなプログラムを 記述する必要 が あります。
-
"button_press_event"
に対する イベントハンドラ で、マウス が 押された座標 を 記録 しておく -
"button_release_event"
に対する イベントハンドラ で、上記 で 記録した座標 と、マウス が 離された座標 を 比較 し、一致していた場合 に クリック に対応する 処理を行う
上記の処理 の 記述 は 面倒 なので、先程はマウスをクリックした際に着手を行うと記述しましたが、マウス が 押された時 に 着手 を 行う ことにします。
下記 のプログラムは、マウスが押された という イベント に、on_mouse_down
という イベントハンドラ を 結び付ける プログラムです。
fig.canvas.mpl_connect("button_press_event", on_mouse_down)
上記 のプログラムを 実行後 に、先程作成した 画像 の Axes の 範囲内 で マウス を 押すたび に、下記の 実行結果 のように、画像の下 に クリック した Axes の座標 が 表示 されます。また、3 行目 のように、Axes の 範囲の外 で マウスを押す と None
が 表示 されます。
実行結果(下図は、画像なので操作することはできません)
なお、先程 plt.show()
を 実行 するとで、2 つの画像 が 描画 されていますが、どちらの画像 の上で マウスを押しても、on_mouse_down
が 呼び出され て Axes の座標 が 表示 されます。ただし、表示 されるのは、最初の画像 の 下だけ で、2 つ目 の 画像の下 には 表示されません。これは、print
で行われる 文字列の表示 が、matplotlib の 画像 とは 関係のない場所 に 行われる からです。
上記の方法 を 利用 することで、〇×ゲーム の ゲーム盤の画像 の上で マウスを押した 際の、ゲーム盤の座標 を 知ること が できます。
今回の記事のまとめ
今回の記事 では、ipywidgets をインポートし、ボタン などの ウィジェット を 配置する方法 と、ウィジェット を 操作 した際に 実行 する イベントハンドラの定義 の 方法 を説明しました。また、ipywidgets の 仕組 みとして、イベントループ について 説明 しました。
次に、matplotlib の インタラクティブな機能 を 利用 するために 必要 な ipympl の 使い方 を 説明 し、実際 に matplotlib で 描画 した 画像の更新 や、その上 で マウス を 押した際 に処理を行う イベントハンドラ の 定義の方法 を 説明 しました。
次回の記事 では、これらの機能 を使って、〇×ゲーム を GUI で遊べる ようにします。
本記事で入力したプログラム
以下のリンクから、本記事で入力して実行した JupyterLab のファイルを見ることができます。
なお、下記のリンクをクリックして、github 上の JupyterLab の ファイル を 見た場合 は、ボタンなど の ウィジェット は 表示されません。また、%matplotlib widget
を 実行した後 の セル の matplotlib の 画像 は、インタラクティブな画像 には なりません。実際 の プログラムの挙動 を 確認したい人 は、下記のファイル を ダウンロード後 に、自分のパソコン で 実行する必要 が あります。
今回の記事では marubatsu.py は変更していません。
次回の記事
-
widget は、window と、小道具 を表す gadget を 合成 してできた 用語 だと 言われています ↩
-
インタラクティブ(interactive)とは、双方向の という意味を表す英語です ↩
-
ipympl の mpl は matplotlib の 略 を表します ↩
-
ヘッダ(header)とは、先頭 の データ のことで、人間 を 上から見た 時に、頭(head)が 最初 にあることが 由来 です。visible は、目に見える という意味を表す英語です ↩
-
フッタ(footer)とは、末尾 の データ のことで、人間 を 上から見た 時に、足(foot)が 最後 にあることが 由来 です。なお、ヘッダ と フッタ の 間 にある、本体のデータ の事を、体 を表す ボディ (body)と呼びます ↩
-
resize とは 大きさ(size)を 変更 する(re)という意味の英語で、可能 を表す able をつけた resizable は、大きさを変更可能 という意味を表します ↩
-
同様 に、Axes に対しても、pyplot が 現在処理の対象 とする current Axes があります ↩